{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Customize your own GPU Kernels in gQuant\n",
    "\n",
    "The gQuant is designed to accelerate quantitive finance workflows on the GPU. The acceleration on GPU is facilitated by using cuDF dataframes in the computation graph. The cuDF project is a continously evolving library that provides a pandas-like API. Sometimes, the data scientists are facing a few challenges that cannot be easily solved:\n",
    "    1. The quantitative work needs customized logic to manipulate the data, and there are no direct methods within cuDF to support this logic.\n",
    "    2. Each cuDF dataframe method call launches the GPU kernel once. For performance crtical task, it is sometimes required to wrap lots of computation steps together in a single GPU kernel to reduce the kernel launch overheads. \n",
    "The solution is to build customized GPU kernels to implement them. The code and examples below illustrate a variety of approaches to implement customized GPU kernels in Python."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys; sys.path.insert(0, '..')\n",
    "\n",
    "# Load necessary Python modules\n",
    "import sys\n",
    "from gquant.dataframe_flow import TaskGraph, Node\n",
    "import cudf\n",
    "import numpy as np\n",
    "from numba import cuda\n",
    "import cupy\n",
    "import math\n",
    "import dask\n",
    "import dask_cudf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Define a utility function to verify the results:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def verify(ground_truth, computed):\n",
    "    max_difference = (ground_truth - computed).abs().max()\n",
    "    # print('Max Difference: {}'.format(max_difference))\n",
    "    assert(max_difference < 1e-8)\n",
    "    return max_difference"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Example Problem: Calculating the distance of points to the origin\n",
    "\n",
    "The sample problem is to take a list of points in 2-D space and compute their distance to the origin.\n",
    "We start by creating a source `Node` in the graph that generate a cuDF dataframe containing 1000 random points."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class PointNode(Node):\n",
    "\n",
    "    def columns_setup(self,):\n",
    "        self.required = {}\n",
    "        self.addition = {'x': 'float64',\n",
    "                         'y': 'float64'}\n",
    "\n",
    "    def process(self, inputs):\n",
    "        df = cudf.DataFrame()\n",
    "        df['x'] = np.random.rand(1000)\n",
    "        df['y'] = np.random.rand(1000)\n",
    "        return df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The distance can be computed via cuDF methods. We define the DistanceNode to calculate the distance and add a `distance_cudf` column to the output dataframe. We use that as the ground truth to compare and verify results later."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DistanceNode(Node):\n",
    "\n",
    "    def columns_setup(self,):\n",
    "        self.required = {'x': 'float64',\n",
    "                         'y': 'float64'}\n",
    "        self.addition = {'distance_cudf': 'float64'}\n",
    "\n",
    "    def process(self, inputs):\n",
    "        df = inputs[0]\n",
    "        df['distance_cudf'] = (df['x']**2 + df['y']**2).sqrt()\n",
    "        return df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Having these two nodes, we can construct a simple task graph to compute the distance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAP8AAACbCAYAAACtbU2GAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nO3de1RTV74H8G+AhDyA8IYYeVpQ3ijV2IqDisXnFIVWb6vt2LlVr3M7Hdtpx5n2tp22c2est7XTO3WmLXdWl13T5bNVi4/WQkUFgSoq74eCPITwfiWBBEh+9w9XzjIGLCAQIPuz1lmQk5OzfxzOb599ds7Zh0dEBIZhrI6NpQNgGMYyWPIzjJViyc8wVsrO0gEw46enpwft7e1ob29Hb28vVCoVAECv16O7u5tbTiqVwsbGhvtdKBTCxcUFLi4ukEgkFomdGX8s+aeojo4OlJaWoqqqCnV1ddxUU1OD1tZWtLe3Q6fTPXA59vb2cHFxgbu7O/z8/ODj48NNgYGBCAkJgaur6xj8RcxE47He/snNYDCgpKQEubm5KCgoQElJCYqLi6FUKgEAAoEAcrkcPj4+8PPzg6+vLzw8PODq6goXFxfup1AohLOzM7deFxcX7veuri4YDAYAgFqtNmkxdHR0oL29HS0tLaipqUFtbS3q6upw+/Zt9PX1AQC8vb0RFhaGkJAQREZGQqFQICwsDLa2thO4pZiRYsk/yWi1Wly8eBEZGRnIycnB5cuXoVKpIBaLER4ejvDwcISEhHA/fX19wePxJjxOIkJdXR1KS0tRXFyMkpISFBUVoaioCBqNBg4ODnj44YexcOFCxMXFIS4uDiKRaMLjZIbGkn8SKC8vx+nTp/Hdd9/hwoUL6O3txZw5c6BQKKBQKLBw4UJERETAzm7yn6Xp9XoUFRUhJycHubm5yMnJQWlpKYRCIRYvXoyEhASsXr0aoaGhlg7V6rHkt5Dq6mqcOHECR44cQVZWFtzc3LBs2TIsX74cK1asgJ+fn6VDHDPNzc04f/480tLScPLkSTQ0NCA0NBRPPvkkNm7ciJCQEEuHaJVY8k8gjUaDAwcO4JNPPkFeXh68vLyQnJyMDRs2IDY21irOkQ0GAy5duoTDhw/j6NGjUCqViI6Oxvbt27F582Y4ODhYOkSrwZJ/Aty8eRMff/wx9u/fD61WiyeffBJbtmxBXFycVST8UAwGAy5cuID9+/fj0KFD4PP5eOaZZ/DCCy9gzpw5lg5v+iNm3JSVldHmzZvJ1taWAgMD6b333qOWlhZLhzUptbW10d69eyk4OJhsbGxo48aNVFRUZOmwpjWW/OOgtraWnn76abK1taWQkBD68ssvSa/XWzqsKUGv19ORI0coIiKCbGxsaMOGDVRVVWXpsKYllvxjqK+vj9577z2SSCQUFBREBw4cYEk/SgaDgY4ePUpz5swhkUhE7777Lmm1WkuHNa2w5B8jV69epdDQUBKJRPTOO++wHXWM6HQ62r17N0kkEgoODqbc3FxLhzRtsBt7xsDf//53PProo/Dy8kJxcTHeeOMN2NvbWzqsaUEgEGDXrl0oKSmBv78/Fi9ejA8//BDE+qkfnKVrn6lMp9PRU089Rba2tvTHP/6RBgYGLB3StKbX6+kvf/kL2dnZUVJSEvX29lo6pCmNfdU3Sj09PUhOTkZ2dja++uorxMfHWzokq3Hx4kWsW7cOUVFROHHiBBwdHS0d0pTEkn8Uent7sXLlSpSUlODbb79FTEyMpUOyOoWFhUhISICfnx/S0tLYxUGjwM75R2Hbtm0oKirC+fPnWeJbSEREBC5cuIBbt25hy5YtrA9gFFjyj9DevXtx4MABHDhwYErdnKJWqxEUFIS1a9daOpQxExQUhMOHD+Obb77Bn//8Z0uHM+Ww5B+B6upqvP766/jv//5vJCQkWDqcESEiGAwG7r79B+Hg4IDY2NgxiOrBxcXFYc+ePXj77bdRXl5u6XCmFHbOPwIbNmxAQUEBCgsLwefzLR2OxTg4OCA6OhqZmZmWDgXAnduI582bB19fX6Smplo6nCmDHfmHqbS0FEePHsWePXusOvEnI1tbW3zwwQc4efIk8vLyLB3OlMGSf5gOHjwIuVw+bufM77//Png8Hng8HmbOnInLly8jPj4ejo6OEIvFWLp0KbKyssw+19bWhpdffhmzZs2CQCCAi4sLVq1ahXPnznHLHD9+nFs3j8eDVqsddH51dTU2btwIZ2dnuLm5Ye3ataisrDSLUaPRICsri/vc3YOM6HQ6vPnmm5gzZw7EYjFcXV3x85//HN988w30ev24bDsAWL58OYKCgnDo0KFxK2PasdwlBlNLWFgY7dy5c9zLiYqKIolEQo888ghdunSJ1Go1Xb58mSIjI0kgEFBGRga3rFKppICAAPLy8qLU1FTq6uqi8vJySkpKIh6PRykpKSbrTkxMJABmF8cY5ycmJnJlfv/99yQSiWj+/PlmMUokElq0aNGg8T///PMklUrp7Nmz1NPTQ42NjfTKK68QADp37tyDb6D7eO211yggIGBcy5hOWPIPw8DAANna2tKRI0fGvayoqCgCQNeuXTOZX1BQQAAoKiqKm7dlyxYCQAcOHDBZVqvV0owZM0gkElFjYyM3/6eSPzU11WT+E088QQDMbkO+X/IHBATQo48+ajY/ODh43JP/1KlTBIBUKtW4ljNdsGb/MDQ0NECv12PmzJkTUp5EIkF0dLTJvIiICMyYMQP5+fncyL3Hjh0DAKxZs8ZkWXt7e8THx6O3txfffffdsMudP3++yWsfHx8Ad/7+4Vq5ciUuXbqEbdu2IScnh2vql5eXY8mSJcNez2gY462vrx/XcqYLlvzD0NXVBQBwcnKakPLuHmL7bp6engDujImn0+nQ1dUFoVA46OWtXl5eAIDGxsZhlyuVSk1eCwQCABjR14P79u3DF198gaqqKsTHx8PJyQkrV67kKqrxZIy/s7Nz3MuaDljyD4NMJgMA7og73tra2ga9Yq25uRnAnUrA3t4eUqkUWq2WexLP3ZqamgDcGVN/rN1vqHAej4dnnnkGaWlp6OzsxPHjx0FESEpKwt69e8c8lrsZWyhyuXxcy5kuWPIPg5ubG8RiMaqrqyekPK1Wi8uXL5vMKywsRENDA6KiorjKaP369QCAU6dOmSyr0+mQnp4OkUiEFStWjHl8YrGYe2AHAMyePRufffYZgDutlrKyMgAAn8/HY489xn2rcG+cY626uhq2trbjUuFNRyz5hyk2NhanT5+ekLKkUilee+01ZGdnQ6PR4MqVK9i8eTMEAgE++ugjbrm//OUvCAgIwM6dO3Hy5EmoVCpUVFTg6aefhlKpxEcffcQ1/8fSvHnzUFFRgbq6OmRnZ6OqqgqLFy/m3v+P//gPFBQUQKfTobm5GXv27AERYdmyZWMey91OnTqFRYsWTYnnG0wKFu5wnDL+7//+j0QiEXV3d49rOVFRUSSXy6mkpIRWrFhBjo6OJBKJKC4ujjIzM82Wb21tpZ07d1JAQADx+XySSqW0YsUKSk9P55Y5duwYATCZNm3aRNnZ2WbzX3/9dSIis/lr1qzh1ldWVkaLFy8miURCPj4+tG/fPu6969ev0/bt2ykkJITEYjG5urrSwoULKSUlhQwGw7htt97eXnJycqK//e1v41bGdMMu7x2mjo4O+Pv747e//S3efPPNcSsnOjoara2tuH379riVMR29//77eOutt1BVVTUurZ3piDX7h8nFxQWvvfYadu/ejdraWkuHw9ylubkZf/rTn/C73/2OJf4IsCP/CGi1WoSFhSEoKAinTp0alwdusCP/yBgMBiQlJeHq1asoKyuDWCy2dEhTBjvyj4BQKMTRo0dx8eJF/OEPfxjTdRuvm8/Pz0d9fT14PB7+67/+a0zLmI7efvttnD59Gl9++SVL/JGybJfD1PTFF18Qj8ejjz76yNKhWLWUlBTi8Xj02WefWTqUKYkl/yjt2bOHeDwevfPOO5YOxSq9//77xOPx6O2337Z0KFMWS/4H8Omnn5KNjQ1t376dDSM9QXQ6Hb344ous5TUGWIffAzp+/Diee+45+Pv749ChQwgODrZ0SNNWVVUVNm7ciPLycqSkpGDjxo2WDmlKYx1+D2jdunXIy8sDn89HTEwM/vrXv2JgYMDSYU0rer0e+/btw7x58zAwMIArV66wxB8Llm56TBc6nY7eeOMNsre3p8jISLp48aKlQ5oWcnJyaN68ecTn82nXrl3s9GoMsSP/GBEIBHjnnXdQVFQEuVyOxYsX47HHHjO7QYcZnuLiYjz77LNYtGgRHB0dce3aNezevRtCodDSoU0bLPnH2EMPPYTTp08jNTUVnZ2dUCgUSExMRE5OjqVDmxLy8vLwxBNPICIiAgUFBThy5AgyMjIQFhZm6dCmHZb842Tt2rX48ccf8c0330CpVOKRRx5BTEwMUlJSoNFoLB3epNLb24vPP/8cCoUCDz/8MCorK/HVV1/h2rVr3G3LzNhjvf0T5NKlS/jHP/6BI0eOQCgU4sknn8SGDRuwbNmycblMeLLT6/U4f/48Dh8+jMOHD0Oj0SApKQk7duzAz372M0uHZxVY8k+w1tZW7N+/HwcOHEBeXh48PDyQnJyMxMRE/OxnP5vWl6j29vYiMzMTJ06cwFdffYXGxkZER0fjqaeewpYtW7hhypiJwZLfgiorK3Ho0CEcOXIE169fh1AoRGxsLBISEhAfH4/IyMgpPTCFXq9HYWEh0tPTcfbsWVy8eBG9vb2IjIzkWj7sugjLYck/SSiVSpw9exZnz55FWloampubIZFIEBMTg4ULF2LhwoWIjo6Gv7//fcfQs6Samhpcv34dOTk5yMnJwZUrV6BWq+Hu7o74+HgkJCQgISFhwkZBZu6PJf8kZDAYUFJSgtzcXGRnZyMnJwelpaUwGAyQSCQICQlBaGgoQkNDERgYCB8fH/j5+cHb23vcKwalUom6ujrU1taiqqoKpaWlKC4uRmlpKdRqNXg8HkJCQqBQKLhKKzw8HDY2rG95smHJP0V0d3ejuLiYS7SioiKUlZXh9u3b3NDaAoEAM2fOhJeXF1xcXODq6gpXV1e4uLhAJBLBycmJ61w0/m4wGLihyY2/a7VatLe3o6OjA+3t7Whvb0dLSwvq6uqg0+kAADY2NpDL5ZgzZw7CwsK4yigsLGzIoceZyYUl/xTX39+P+vp61NXVobq6GnV1dWhtbTVJ3o6ODvT29kKtVqO/vx/AnWcRGAwG8Hg8Llnt7Ozg6OgIoVDIVRrGSsTd3R2+vr7w8/ODj48P5HI5N64/MzWx5LdSN27cQHBwMK5evYq5c+daOhzGAtiJGMNYKZb8DGOlWPIzjJViyc8wVoolP8NYKZb8DGOlWPIzjJViyc8wVoolP8NYKZb8DGOlWPIzjJViyc8wVoolP8NYKZb8DGOlWPIzjJViyc8wVoolP8NYKZb8DGOlWPIzjJViyc8wVoolP8NYKZb8DGOlWPIzjJViyc8wVoolP8NYKZb8DGOlWPIzjJViyc8wVoolP8NYKZb8DGOlWPIzjJViyc8wVopHRGTpIJjxZTAYsHz5crS0tHDz+vv7cevWLfj5+cHe3p6bL5VKce7cOfD5fEuEykwgO0sHwIw/Gxsb+Pr6IiMjA/fW9Tdu3OB+5/F4SEpKYolvJViz30o8/fTTZok/mM2bN09ANMxkwJr9VkKv18PT0xPt7e1DLiMWi9HW1gahUDiBkTGWwo78VsLW1habNm2CQCAY9H0+n4+NGzeyxLciLPmtyFNPPYW+vr5B3+vv78fTTz89wRExlsSa/VaEiODr64vbt2+bvefm5oampibY2tpaIDLGEtiR34rweDw888wzZr35AoEAmzdvZolvZVjyW5nNmzejv7/fZF5fXx+eeuopC0XEWApr9luhkJAQlJWVca9nzpyJ2tpa8Hg8C0bFTDR25LdCzz77LNf0FwgE2LJlC0t8K8SO/FaopqYGAQEB3EU/RUVFCAsLs3BUzERjR34r5Ofnh5iYGADAnDlzWOJbKXZt/xQzMDAAlUoFAOjo6AAAqFQqDAwMAAC0Wi16e3sH/Wx/fz/UajUAICIiAleuXEFMTAyOHDkCAJBIJENeBCQSibgLgOzs7ODo6AgAcHFxAQA4OjrCzo7tTlMJa/aPIyJCW1sb2tvb0d7ejq6uLnR3d6O7uxtqtRoajQbd3d3o6uriXqtUKnR2dkKj0UCn00GtVqO/v/++ST2ZGCsJPp8PBwcHCAQCODg4wNnZGQ4ODnBwcIBEIoGzszMcHR2511KpFE5OTnBycoKrqyvc3Nzg6uoKGxvWOB0vLPlHoKenB0qlEo2NjWhqakJDQwOX3Hcn+d2vB+Pk5MTt9MYdfrDXQqHQLJlsbGwglUoB3Ln91sbGxuSIbWtrCycnp0HL5fF4cHZ25l6/8847ePPNN7nXnZ2dQ978093dDb1eD+DOV4MajQYGgwFdXV0AgK6uLhgMBmg0GvT19UGn06GnpwdarRYajQZdXV1QqVRcJTfY68G4uLhwFYHxp3Fyd3eHTCaDl5cXvLy8MGPGDEgkkvv9C5m7sOTHnZ359u3bqK2t5abm5mYu0Zubm9HQ0MA1mY08PT3h5uZmslPe/bu7u7vJPOPRbbIYGBiYVE11lUqFrq4uk4q0tbXVrHK9u8Jtbm42qbAkEglmzJjBVQgymQyenp7w9fXlJh8fnyFPb6yJVSR/X18fqqqqcPPmTZMEr62tRXV1NZRKJQwGAwBAKBRi5syZ8Pb25o4mHh4ekMvl8PT0hLe3N7dDsfveLW9gYADNzc1obGyEUqlEc3Mz6uvrTSrvpqYm1NXVQavVArjTApLJZPD39zepFPz8/BAYGIhZs2aZDHAyXU2r5G9oaEBJSQmqqqq4qbi4GOXl5VyTVSgUYsaMGQgMDOQmmUzGzfP392fnmdNUR0cHqqqq0NDQAKVSye0jxte3bt3iWhEymQxhYWEm+0loaChmz549qVpLD2JKJn91dTUKCwtRWFiI/Px8FBUV4ebNm9wdax4eHggKCkJwcDCCgoK46aGHHoKDg4OFo2cmK41Gg5s3b+LGjRsmU0VFBZqbmwHcufV51qxZiIiIQGRkJPfT399/yl0oNamTX6fT4fr167h27RoKCgq4hO/q6gKPx0NAQAAiIyMRHh6OkJAQLsnv7tRimLHQ1dXFVQZlZWUoLCxEQUEBqqqqQERwcnJCeHg4IiIiEBUVhejoaMydO3dSj48wqZK/oaEBeXl5yMrKQmZmJvLy8qDVauHk5ISgoCCEhoYiJiYGYWFhmDt3Ltzc3CwdMmPl+vr6cOPGDeTl5SEvLw8lJSUoKChAc3Mz7OzsEBwcjJiYGMTGxmLRokUICQmZNKeVFkt+vV6PK1euID09HVlZWcjNzUVbWxsEAgGio6OhUCiwYMECKBQKBAUFWSJEhhm1yspK5Obm4scff0Rubi6uXbsGnU4HFxcXKBQKLFq0CPHx8Zg/f77F+hAmNPlLSkqQnp6O9PR0nD9/Hp2dnZDJZFiyZAkUCgUUCgXmzp1rFT2tjHXp6+vDtWvXuMogIyMD9fX1cHJyQlxcHOLj4xEfH4/w8PAJi2lck1+n0yEtLQ1ff/01zpw5A6VSCalUiiVLlnB/bGho6HgVzzCTWnl5OdLS0pCeno6MjAx0dHTA29sbK1euxPr165GQkDCufQZjnvwajQZnzpzB119/jVOnTkGlUmHBggV4/PHHER8fj4cffpiNGMMw99Dr9bh27RrS0tKQmpqKnJwciMVirF69GsnJyVi9evWYf1M1JslPRDh37hw+/fRTpKamoq+vD7GxsUhKSsL69evh4+MzFrEyjNVoaGjAsWPH8PXXX+P8+fPg8/lYs2YNtm3bhuXLl49Jp+EDJX97ezv279+PTz/9FOXl5Xj00UexZcsWJCYmwtPT84GDYxgGaG1txYkTJ7B//35cvHgRDz30ELZt24bnnnsO7u7uo18xjUJFRQX98pe/JJFIRI6OjrRjxw7Kz88fzaoYhhmBoqIieuGFF0gqlZK9vT09++yzdPPmzVGta0TJX1NTQ8888wzZ2trS7Nmz6ZNPPqHu7u5RFTzWDhw4QAAIANnb21s6nGnhf/7nf7htKpfLLR3OhBnOvnTw4EGKiooioVDILVtYWDhhMarVakpJSaHZs2cTn8+n//zP/6TGxsYRrWNYya/T6ejtt98msVhMQUFB9K9//YsGBgZGFfR4i4+PN/uHqVQqeuihh2jNmjUWimpqi4qKsqrkNxpsXyIiyszMJB6PR6+++iqpVCq6efMmzZw5c0KT36i/v59SUlJILpeTg4MDvfXWW9Tb2zusz/5k8hcVFVF0dDQ5ODjQnj17SKfTPXDA42mwf1h3dzcFBgbSqlWrRr1eiURCixYtetDwpiSW/KZ+85vfEAC6ffu2BaIaXE9PD7333nvk5ORE4eHhdP369Z/8zH27DM+cOYNHHnkEYrEY169fx6uvvjol74N2dHREZWUlTp8+belQmGmgrq4OACbV5eUikQi/+93vkJ+fDzc3NyxatAjHjx+/72eGTP6TJ0/i8ccfR3JyMs6dO4dZs2aNecAMMxUZbw+fjPz9/ZGeno7nnnsOycnJOHz48NALD9YcKCkpIZFIRFu3biWDwTDmTZSxUFpaSomJieTk5ERisZhiY2Pp4sWLZk21Y8eOcR0yAEzOh7RaLb3xxhs0e/ZsEolE5OLiQmvXrqUTJ05wfRp3d3rdPdna2nLr6e/vp4MHD9Ly5cvJy8uLhEIhhYeH01//+lfS6/VDxnLr1i3asGEDSaVScnV1pTVr1gzac9va2kovvfQSBQYGkkAgILlcTvHx8fT5559TT0+PybLNzc3061//mvz8/IjP55O7uzutX7+erl27NuptbWz2l5aW0urVq8nJyYlEIhEtWbKEMjMziYioo6PDbBu9++673Pa5e35ycvKIYxjONnj33Xe5Mu4+RTtz5gw3383NzWzdo92XjJNCoRjx3zMRXnzxRbK3tx/yFGDQ5I+Li6MFCxZM2k69GzdukLOzM8nlcjp79iypVCoqKCighIQE8vf3H/Q8LTEx0Sz5n3/+eZJKpXT27Fnq6emhxsZGeuWVVwgAnTt3zuTz9zvnT01NJQD05z//mdrb26mlpYX+93//l2xsbOiVV14ZMpbExES6dOkSqdVq+v7770kkEtH8+fNNllUqlRQQEEDe3t6UmppK3d3d1NjYyO3oH374IbdsQ0MD+fn5kZeXF506dYpUKhUVFRVRXFwcCYVCunTp0kg2MycqKoqkUiktXbqUMjMzSaVS0eXLlykyMpIEAgFlZGRwy65YsYJsbGwGrcQeeeQR+vLLL0dc/ki2AdHQ/6uYmBiz5B+rfWky0uv1tHjxYlqwYMGg75slf35+PgGg8+fPj3two/Xkk08SADp69KjJ/Pr6erK3tx/2PywgIIAeffRRs2WDg4NHnPxLliwxm79582bi8/nU1dU1aCypqakm85944gkCQC0tLdy8LVu2EAA6dOiQ2fpXrlxpsuP/4he/IABmCaZUKsne3p5iYmIGjf+nREVFEQDKzs42mV9QUEAAKCoqipv33XffEQD61a9+ZbJsZmYmyeVy6uvrG3H5I9kGRCNL/rHalyary5cvD/q/Ixok+T/++GNyd3eftM19IiJHR0cCQCqVyuy9iIiIYf/DduzYQQBo69atlJ2dfd+Wzmh6+42nDPcecY2x3Pu97EsvvUQATC6YkkqlBGBY11NIpVKysbExq2yIiObNm0cAqK6ubkR/AxFx32cPtk/MmDGDAFBDQwM3LyIigsRiMbW2tnLzEhMTaffu3SMum2hk24BoZMk/VvvSZObr60t79uwxm2/W4dfR0QE3N7dJOySRTqeDSqWCUCgc9EaHkVxWvG/fPnzxxReoqqpCfHw8nJycsHLlShw7dmxEMXV1deHNN99EREQEXFxcwOPxwOPx8OqrrwK4M+T3YIxDcBsZv0kxDiaq0+nQ1dUFoVDIPSRjKMZlDQYDpFIpF4Nxunr1KgDgxo0bI/rbjIbaJ4zb2zjMFQDs3LkTPT09+Pvf/w4AqKiowA8//IBt27aNuNyRbIPRrHus9qXJzN3dHW1tbWbzzZLfz88PtbW10Gg0ExLYSNnb28PR0RFardZsKG0AQ46VPxjj8+rT0tLQ2dmJ48ePg4iQlJSEvXv3mi07lJ///Od49913sXXrVlRUVMBgMICI8OGHHwLAkGPh/xR7e3tIpVJotVruKT33W9bZ2Rl2dnbo7+8H3WnVmU1Lly4dVSxDjatvTPq7E2XTpk3w8vLCxx9/DJ1Ohw8++AC/+MUvuKf7jMRItoGRjY0NN57j3To7O83WPVb70mSl0+lQWVmJgIAAs/fMkn/16tUwGAz44osvJiS40Vi1ahUA4NtvvzWZ39raivLy8mGvx9nZmXtUNZ/Px2OPPYbjx4+Dx+Ph1KlTJsuKxWKTHWr27Nn47LPPoNfrkZWVBW9vb7z44ovw8PDgKoqxeMLO+vXrAWDQaxTmzp2Ll156iXudlJSEgYEBZGVlmS373nvvwdfXl3us10ip1Wrk5+ebzCssLERDQwOioqIgk8m4+fb29vjVr36F5uZmfPDBB/jyyy/xm9/8ZlTlAiPbBsCdkXfr6+tN5jU2NqK2ttbs82O1L01WBw8ehEajweOPP27+5mDnCC+//DK5uLhQdXX1eJ2GPJCbN2+Sq6urSQ9tcXExrVixgjw9PYd9niaVSikuLo7y8/NJq9VSU1MT/fGPfyQA9Kc//cnk8ytXriSpVEq1tbV06dIlsrOzo5KSEiIiWrZsGQGgPXv2UEtLC/X09NAPP/xAvr6+BIC+//77n4yFiGjXrl0EwORrOWNPt0wmo5MnT1J3dzfV1dXRjh07yMvLi2pqarhlm5qaaNasWRQYGEinT5+mzs5Oamtro08++YTEYvGgHWbDERUVRRKJhGJjYyknJ4fUavWQvf1GLS0tJBKJiMfjUWJi4qjKNRrJNiAieuGFFwgA/e1vf+Muv92wYQPJ5XKzc/6x2pcmo/r6evLw8KAdO3YM+v6gya9Wq6J1t1kAAAgISURBVCkyMpJmz55NSqVyXAMcrfLyclq3bh33nfP8+fPp5MmTFB8fz33/+u///u+Dfje7adMmIiK6fv06bd++nUJCQkgsFpOrqystXLiQUlJSzDq3ysrKaPHixSSRSMjHx4f27dvHvdfS0kLbt28nHx8f4vP55OXlRVu2bKHf//73XJkxMTGUnZ1tFsvrr79ORGQ2/+77EFpbW2nnzp0UEBBAfD6fZDIZ/du//RtVVFSYbZe2tjZ6+eWXKTAwkPh8Pnl4eFBCQoJZBTQc997Y8+OPP9LSpUvJwcGBRCIRxcXFcd/zD2br1q1j9s3RSLZBZ2cnPf/88ySTyUgkElFsbCxdvnyZYmJiuL9n165d3PIPsi9hiJ50S2tpaaGIiAgKCQkZtAOYiGjI+/mVSiXi4uLQ19eHb775BpGRkQ/S+mCs0Oeff459+/bhypUrlg7FqpSUlODxxx+HXq/HhQsXhhxMZ8jLe2UyGbKzs+Hn5weFQoG9e/dyvdAMMxyffPIJXn75ZUuHYTUMBgP27duH+fPnw8vLC7m5ufcfReunmg/9/f20e/duEggENHfuXLpw4cIYN1CY6SIlJYXWrVtHKpWK/vGPf1BQUBD19/dbOiyr8P3339O8efPIzs6Odu3aNay7b4c9mEdZWRmtXr2aANDy5cspNzf3gYJlLAeDnLfeO7311lsjXm9KSgoBIDs7O4qMjKS8vLwJj8HaXL58meubWL58+bBu5TUa8TBep0+fpgULFhAAWrp0KR08eHDS3+PPMNNJX18fHT16lB577DHi8Xi0aNEiunjx4ojXM+IhQFetWoXc3Fx8++23cHR0xKZNm+Dr64vXXnsNt27dGunqGIYZptraWrzxxhvw8/PDhg0bwOfzcfLkSWRmZiI2NnbE63vgobvr6uqQkpKCf/7zn2hsbERcXBySk5Oxbt06yOXyB1k1w1i9xsZGbgjvc+fOwcPDA7/85S+xdetW+Pv7P9C6x+yhHQMDAzh58iQOHjyI06dPQ6PRQKFQIDk5GUlJSYNeXsgwjLmamhocO3YMX331FS5dugSRSIRVq1Zh48aNSExMBJ/PH5NyxuVxXVqtFmfPnsXXX3+N1NRUtLe3IyIigntEV1xc3JjfpMEwU5VarcaFCxfwww8/ID09Hfn5+ZBKpVi7di2Sk5OxYsUKiESiMS933B/U2d/fj4yMDJw5cwbp6ekoLCyEra0tFixYwFUGCxcuZA/nZKxGX18fcnNzuYfW5ubmYmBgAGFhYYiPj8fKlSuxbNmycR8vc8If0d3S0oKMjAykpaUhMzMTJSUl3HPMjc8wj4mJQWho6KS9rZhhRqKhoQF5eXnIy8tDVlYWsrKy0NvbC5lMhtjYWCxfvhyrV6/GzJkzJzSuCU/+e926dQuZmZnIzc1Fbm4u8vPz0d/fD09PTyxYsAAKhQIPP/wwIiIiWAciM+kplUoUFhbiypUr3D7d1NQEOzs7REZGco+ij42NtfiguBZP/nv19vbi6tWr3HPMc3JyUFNTAwBwdXVFVFQUIiIiEBERgcjISISFhUEikVg4asba9PT0oLi4GAUFBSgsLERhYSEKCgrQ2toKAPDx8YFCocDChQuhUCgwb948iMViC0dtatIl/2A6OjrMNnJRURHUajVsbGwQGBiIOXPmIDg4GEFBQdzk4+PDTh2YUSMi3L59Gzdu3OCmiooKlJWVobKyEgaDARKJBGFhYYiMjDQ5KE2mMf2HMiWSfzBEhKqqKhQUFKC4uBhlZWXcP6ijowPAnQcZ3F0ZzJo1C76+vvD19YWfn9+49KAyU4tWq0VNTQ1qa2tRW1uLyspKk2Q3DsHm7OzM7UezZ89GeHg4IiMjERgYOCaPy7aEKZv899PS0sLV0hUVFdw/srKy0mS4Jk9PT/j4+HAVgr+/P3x9fSGXyyGXy+Hp6Tkln1DE3NHX14fm5mY0NDSgvr4etbW1qK6u5hK9rq4OTU1N3PISiQSzZs3ikjw4OJhrTU6X8fzuNi2T/37a29tRV1dntiPU1taipqYGjY2NJmPuubu7w8vLC97e3pDJZPDy8sKMGTPg6emJGTNmwMPDA66urnB1dWUtiQmg1WrR3t6OtrY2tLW1ob6+nkvwpqYmKJVKNDY2oqmpCS0tLSaflclkXEV/b4Xv4+MzJZrqY8nqkv+n6HQ6NDQ0QKlUorm52Wznamxs5N67d5BIkUgEV1dXuLm5cRWC8bWbmxukUimkUikcHBwgkUjg6OgIZ2dnSCQSODg4DDqC7HSjVquh0WigVqvR2dkJtVrNzevs7ERXVxeX3Hf/NE73joTM5/Ph6ekJmUwGb29vk8rZ2HqTyWSQy+XsWpJ7sOR/AC0tLUPupHdPxqNUV1cXN7z2UJydnbnKwcHBAba2tnBycuLe4/F4cHR0hJ2dHUQiEYRCIfh8vknFYWdnN+QVlMbP3Eun0w05xLhKpTIZ+FOtVqO/vx9arRa9vb0YGBjgRtY19rcYP2NMdI1Gw703GB6PB2dnZ0il0iEr0LtfG+dNx+b4RGHJbwE9PT3QaDRQqVRmR7+Ojg7udU9PD/r6+qDRaGAwGLjhs40ViEajQV9fn1ni9vb2QqvVDlp2d3f3oA+atLGxMXuOgJFQKDQ5pRGLxbC3t4dAIIBEIjH5rFQqhY2NDSQSCQQCAcRisUkrx9jCkUgkcHFx4V5Ptq/BrAFLfoaxUlPzOwqGYR4YS36GsVIs+RnGStkBOGLpIBiGmXj/DwOJG1bI5oh4AAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "input_node = {\n",
    "    'id': 'points',\n",
    "    'type': PointNode,\n",
    "    'conf': {},\n",
    "    'inputs': []}\n",
    "\n",
    "cudf_distance_node = {\n",
    "    'id': 'distance_by_cudf',\n",
    "    'type': DistanceNode,\n",
    "    'conf': {},\n",
    "    'inputs': ['points']}\n",
    "\n",
    "task_list = [input_node, cudf_distance_node]\n",
    "task_graph = TaskGraph(task_list)\n",
    "task_graph.draw(show='ipynb')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The next step is to run the task graph to obtain the distances. The output is identified by the `id` of the distance node:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "            x         y  distance_cudf\n",
      "0    0.880657  0.915653       1.270424\n",
      "1    0.313161  0.803863       0.862708\n",
      "2    0.715800  0.832247       1.097728\n",
      "3    0.909188  0.575765       1.076164\n",
      "4    0.293410  0.937092       0.981952\n",
      "..        ...       ...            ...\n",
      "995  0.877839  0.285406       0.923070\n",
      "996  0.320840  0.905872       0.961011\n",
      "997  0.941912  0.342269       1.002171\n",
      "998  0.435483  0.489932       0.655499\n",
      "999  0.970076  0.564717       1.122476\n",
      "\n",
      "[1000 rows x 3 columns]\n"
     ]
    }
   ],
   "source": [
    "(out_df,) = task_graph.run(outputs=['distance_by_cudf'])\n",
    "print(out_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Customized Kernel with Numba library\n",
    "\n",
    "Numba is an excellent python library used for accelerating numerical computations. Numba supports CUDA GPU programming by directly compiling a restricted subset of Python code into CUDA kernels and device functions. The Numba GPU kernel is written in Python and translated (JIT just-in-time compiled) into GPU code at runtime. This is achieved by decorating a Python function with `@cuda.jit`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "@cuda.jit\n",
    "def distance_kernel(x, y, distance, array_len):\n",
    "    # ii - overall thread index\n",
    "    ii = cuda.threadIdx.x + cuda.blockIdx.x * cuda.blockDim.x\n",
    "    if ii < array_len:\n",
    "        distance[ii] = math.sqrt(x[ii]**2 + y[ii]**2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Just like a C/C++ CUDA GPU kernel, the `distance_kernel` function is called by thousands of threads in the GPU. The thread id is computed by `threadIdx.x`, `blockId.x` and `blockDim.x` built-in variables. Please check the [CUDA programming guild](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#thread-hierarchy) for details."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A cuDF series can be converted to GPU arrays compatible with the Numba library via.to_gpu_array` API. The next step is to define a Node that calls this Numba kernel to compute the distance and save the result into `distance_numba` column in the output dataframe."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "import rmm\n",
    "class NumbaDistanceNode(Node):\n",
    "\n",
    "    def columns_setup(self,):\n",
    "        self.required = {'x': 'float64',\n",
    "                         'y': 'float64'}\n",
    "        self.addition = {'distance_numba': 'float64'}\n",
    "        self.delayed_process = True\n",
    "\n",
    "    def process(self, inputs):\n",
    "        df = inputs[0]\n",
    "        number_of_threads = 16\n",
    "        number_of_blocks = ((len(df) - 1)//number_of_threads) + 1\n",
    "        # Inits device array by setting 0 for each index.\n",
    "        # df['distance_numba'] = 0.0\n",
    "        darr = rmm.device_array(len(df))\n",
    "        #darr = cuda.device_array(len(df))\n",
    "        distance_kernel[(number_of_blocks,), (number_of_threads,)](\n",
    "            df['x'],\n",
    "            df['y'],\n",
    "            darr,\n",
    "            len(df))\n",
    "        # df['distance_numba'.to_gpu_array()\n",
    "        df['distance_numba'] = darr\n",
    "        return df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `self.delayed_process = True` flag in the `columns_setup` is necesary to enable the logic in the `Node` class for handling `dask_cudf` dataframes in order to use Dask (for distributed computation i.e. multi-gpu in examples later on). The `dask_cudf` dataframe does not support GPU customized kernels directly. The `to_delayed` and `from_delayed` low level interfaces of `dask_cudf` enable this support. The gQuant framework handles `dask_cudf` dataframes automatically under the hood when we set this flag."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Customized Kernel by CuPy library\n",
    "\n",
    "CuPy is an alternative to Numba. Numba JIT compiles Python code into GPU device code at runtime. There are some limitations in how Numba can be used as well as JIT compilation latency overhead. When a Python process calls a Numba GPU kernel for the first time Numba has to compile the Python code, and each time a new Python process is started the GPU kernel has to be recompiled. If advanced features of CUDA are needed and latency is important, CuPy is an alternative library that can be used to compile C/C++ CUDA code. CuPy caches the GPU device code on disk (default location `$(HOME)/.cupy/kernel_cache` which can be changed via `CUPY_CACHE_DIR` environment variable) thus eliminating compilation latency for subsequent Python processes.\n",
    "\n",
    "`CuPy` GPU kernel is esentially a C/C++ GPU kernel. Below we define the `compute_distance` kernel string."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "kernel_string = r'''\n",
    "    extern \"C\" __global__\n",
    "    void compute_distance(const double* x, const double* y,\n",
    "            double* distance, int arr_len) {\n",
    "        int tid = blockDim.x * blockIdx.x + threadIdx.x;\n",
    "        if (tid < arr_len){\n",
    "        distance[tid] = sqrt(x[tid]*x[tid] + y[tid]*y[tid]);\n",
    "        }\n",
    "    }\n",
    "'''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using gQuant we can now define a Node that calls this CuPy kernel to compute the distance and save the results into `distance_cupy` column of a `cudf` dataframe."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CupyDistanceNode(Node):\n",
    "\n",
    "    def columns_setup(self,):\n",
    "        self.required = {'x': 'float64',\n",
    "                         'y': 'float64'}\n",
    "        self.addition = {'distance_cupy': 'float64'}\n",
    "        self.delayed_process = True\n",
    "        \n",
    "    def get_kernel(self):\n",
    "        raw_kernel = cupy.RawKernel(kernel_string, 'compute_distance')\n",
    "        return raw_kernel\n",
    "        \n",
    "    def process(self, inputs):\n",
    "        df = inputs[0]\n",
    "        cupy_x = cupy.asarray(df['x'])\n",
    "        cupy_y = cupy.asarray(df['y'])\n",
    "        number_of_threads = 16\n",
    "        number_of_blocks = (len(df) - 1)//number_of_threads + 1\n",
    "        dis = cupy.ndarray(len(df), dtype=cupy.float64)\n",
    "        self.get_kernel()((number_of_blocks,), (number_of_threads,),\n",
    "                   (cupy_x, cupy_y, dis, len(df)))\n",
    "        df['distance_cupy'] = dis\n",
    "        #df['distance_cupy'] = 0.0\n",
    "        return df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `self.delayed_process = True` flag is added for the same reason as with `DistanceNumbaNode` i.e. to support `dask_cudf` data frames."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Computing using the Nodes with customized GPU kernels\n",
    "\n",
    "First we construct the computation graph for gQuant."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAzoAAACbCAYAAACqGe5iAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nOzdd1xUZ9o//s/ADL333lQUVFAxIgF1FVFRkCJjw5DyxBh385jyTdlkXXWTjeltN8WsqbhGZVRQFBWwJFJEJLHRFInSe+8yc//+yG/OA4IIOHCG4Xq/XvOCOXPm3Ncczlyc65z73EfAGGMghBBCCCGEENWRrsZ3BIQQQgghhBCiaFToEEIIIYQQQlQOFTqEEEIIIYQQlSPkOwBCCCGkvb0ddXV1qKurQ3t7O5qamgAAjDE0NDRw8xkYGEBdXZ37XVNTEyYmJjA2Noaenh4vsRNCCFFOVOgQQggZUY2NjcjNzUVhYSGKi4tRXFyMoqIiFBUVobq6GnV1dejo6HjodjQ0NGBsbAwzMzM4ODjA3t4e9vb2cHBwgKOjI6ZOnQozMzMFfCJCCCFjgYBGXSOEEKIIjDHk5eUhIyMDV69eRXZ2NnJyclBSUgIAEIlEsLGxgb29PRwdHWFvbw9LS0vujIz8p5aWFoyNjbnlGhkZQSAQAACampoglUoBAK2trWhra+POBNXX16Ourg41NTW4c+cOioqKuMKqs7MTAGBmZoZp06bBzc0NHh4e8Pb2xvTp0yEU0nE/QghRMelU6BBCCBmWrq4upKam4uzZs8jIyMDFixfR0NAALS0tTJs2DVOnToW7uzv309HREWpq/FwaWlpaitzcXGRnZ3MF2NWrV9Hc3AwdHR14eXnB29sbCxYswJ/+9CfqBkcIIWMfFTqEEEIG79atW0hISEBiYiLOnj2L1tZWTJgwAT4+PvD29oa3tzdmzJgBkUjEd6gPJJPJkJOTg4yMDFy4cAEZGRm4fv06RCIRfH19sWTJEgQGBsLT05PvUAkhhAwdFTqEEEIGVlRUhNjYWEgkEqSlpUFHRwc+Pj4ICgrCypUr4ezszHeIClNdXY1z584hOTkZCQkJKCkpgZOTE1auXAmxWAw/Pz++QySEEDI4VOgQQgjpq6OjAzExMdi1axfS09NhamqKsLAwrF69GgsXLhwX17QwxnDx4kXExMRAIpGguLgY7u7ueOaZZ/D444/DyMiI7xAJIYTcHxU6hBBC/s+dO3fw5Zdf4rvvvkNjYyPCwsLw5JNPwt/ff0x0RxspjDGkp6cjOjoae/fuBWMM69atw3PPPUdd2wghRDlRoUMIIQT4/fffsXPnTvz444+wsrLCM888g6effhpWVlZ8h6Z0mpqasGfPHuzatQvZ2dlYuXIltm3bhlmzZvEdGiGEkP+Tzs/wN4QQQpRCRUUFnn76aUyePBlnz57F119/jVu3bmHr1q1U5NyHgYEB/vKXv+Dq1as4evQoSktLMXv2bKxcuRL5+fl8h0cIIeT/R4UOIYSMQ1KpFJ9//jmmTJmCpKQk7N69G3l5eXjyySfHdRe1oRAIBAgKCkJmZibi4+NRXFwMDw8P/O1vf0NbWxvf4RFCyLhHXdcIIWScycvLQ2RkJK5fv46XXnoJW7duha6uLt9hjXlSqRRffPEFtm3bBiMjI/z4449YsGAB32ERQsh4RV3XCCFkPImOjsYjjzwCkUiEK1eu4J133qEiR0HU1dWxZcsW5OXlYdasWfD398dbb70FmUzGd2iEEDIuUaFDCCHjgFQqxbPPPosnnngCmzZtwvnz5zFlyhS+w1JJVlZWOHz4MD755BO8/fbbCAwMRHNzM99hEULIuENd1wghRMV1dXVhw4YNOH78OH766SeEhITwHdK4kZWVhaCgIDg6OiIhIQEmJiZ8h0QIIeMFDS9NCCGqrLu7G6GhoTh//jzi4+Mxf/58vkMad27evImAgAAYGBjg559/hrGxMd8hEULIeEDX6BBCiCp76aWXcO7cOSQnJ1ORw5NJkybh/PnzaGxsxNq1ayGVSvkOiRBCxgUqdAghREV9//33+Pzzz/HDDz/gkUce4TucQWtpacGkSZMQFBTEdygKY29vj8OHD+P8+fP461//ync4hBAyLlChQwghKqiqqgovvvgiXn31VURERPAdzpAwxiCTyRQyWpmenh78/PwUENXD8/LywpdffomPP/4YWVlZfIdDCCEqj67RIYQQFbRp0yYcP34c+fn543r4aD09PcyYMQMpKSl8h8KZP38+uru7kZqaCoFAwHc4hBCiqugaHUIIUTWlpaX49ttv8fbbb4/rIkdZffTRR0hPT8eZM2f4DoUQQlQaFTqEEKJiJBIJ9PX1sXbt2hFZ/ocffgiBQACBQAA7OztkZmbC398f+vr60NHRwcKFC5GamtrnfbW1tXjppZcwYcIEaGhowNjYGIGBgTh79iw3T1xcHLdsgUCAjo6Ofqffvn0ba9asgZGREUxNTREUFIRbt271ibG1tZU7cyIQCCAUCrl5Ojs7sW3bNkyZMgU6OjowMTFBcHAwjh49OqIDBjzyyCPw8vLCgQMHRqwNQgghABghhBCV4ufnxx5//PERb8fT05Pp6uoyHx8flpaWxlpaWlhmZibz8PBgGhoa7Ny5c9y85eXlzNnZmVlaWrL4+HjW2NjI8vPzWXh4OBMIBGz37t29lh0SEsIAsPb29n6nh4SEcG0mJSUxbW1t9sgjj/SJUVdXl/n6+vYb/9NPP80MDQ1ZYmIia2trYxUVFezll19mANjZs2cffgUN4P3332dmZmZMKpWOaDuEEDKOpdEZHUIIUTHXr1+Hr6/vqLTV2tqKL7/8Ej4+PtDV1cXs2bPx3//+F11dXXj++ee5+V5//XX8/vvv+PTTTxEUFAQDAwO4urrip59+grW1NbZs2YLKyspBt/v0009zbS5evBgrVqxAZmYmampqBr2M06dPY+rUqQgICIC2tjYsLS3xwQcfwNXVdUjrYDh8fX1RU1ODioqKEW+LEELGKyp0CCFEhbS0tKChoQF2dnaj0p6uri5mzJjRa9r06dNhY2ODK1euoLy8HAAQGxsLAFixYkWveTU1NeHv74/29nacOnVq0O3eO1y2vb09AKCsrGzQy1i2bBnS0tLwzDPP4MKFC1x3tfz8fPzpT38a9HKGQx5vcXHxiLZDCCHjGRU6hBCiQhobGwEABgYGo9KekZFRv9MtLCwA/DHMdWdnJxobG6GlpQV9ff0+81paWgLAkM5uGBoa9nquoaEBAEMakvqLL75AdHQ0CgsL4e/vDwMDAyxbtowrykaSPP6GhoYRb4sQQsYrKnQIIUSFWFpaQl1dnTuTMtJqa2vB+rlLQVVVFYA/Ch5NTU0YGhqio6MDzc3NfeaVd1mzsrJSeHwDDd8sEAjw2GOPITk5GQ0NDYiLiwNjDOHh4fj4448VHktP8jNPtra2I9oOIYSMZ1ToEEKIChEKhbC0tMTt27dHpb2Ojg5kZmb2mnbt2jWUlZXB09MT1tbWAICwsDAAwPHjx3vN29nZidOnT0NbWxtLly5VeHw6Ojro6urink+ePBn/+c9/APxxNiovLw8AIBKJEBAQwI3udm+ciib/+4xWF0NCCBmPqNAhhBAVM2/ePCQkJIxKW4aGhnjjjTeQnp6O1tZWXLp0CRs2bICGhgY+++wzbr533nkHzs7OeOGFF3Ds2DE0Nzfjxo0bWL9+PcrLy/HZZ59xXdgUadasWbhx4waKi4uRnp6OwsJCzJs3j3v92WefxdWrV9HZ2Ymqqiq8//77YIxh0aJFCo+lp+PHj8PDw+O+Xf8IIYQoAM/DvhFCCFGwQ4cOMXV1dVZeXj6i7Xh6ejJbW1uWk5PDli5dyvT19Zm2tjZbsGABS0lJ6TN/TU0Ne+GFF5izszMTiUTM0NCQLV26lJ0+fZqbJzY2lgHo9YiMjGTp6el9pv/tb39jjLE+01esWMEtLy8vj82bN4/p6uoye3t79sUXX3CvXb58mW3atIm5ubkxHR0dZmJiwubOnct2797NZDLZiK237u5uZm1tzd56660Ra4MQQghLEzDWT+dqQgghY1Z7ezucnJywfv16fPLJJyPWzowZM1BTU4OSkpIRa0MVffPNN/jzn/+MvLw8uLi48B0OIYSoqnTqukYIISpGW1sbb731Fr744gvk5+fzHQ7pobm5GX//+9/x3HPPUZFDCCEjjM7oEEKICpJKpZg1axb09PRw5swZaGpqKrwNOqMzdFFRUThx4gRu3LgBY2NjvsMhhBBVRmd0CCFEFamrqyMmJgbZ2dnYtGmTQpf94YcfQiAQ4MqVKygtLYVAIMDWrVsV2oYq+uSTT7B3715ER0dTkUMIIaOAzugQQogKO3r0KMLCwrBt2zZs376d73DGrQMHDmDDhg3YuXMnXnnlFb7DIYSQ8YDO6BBCiCpbuXIlvvrqK7z55pt4+eWX+725JxlZu3fvRmRkJLZs2YKXX36Z73AIIWTcEPIdACGEkJH1zDPPwMDAAFFRUSgvL8fXX38NPT09vsNSed3d3di+fTveeecd/OMf/8Df//53vkMihJBxhbquEULIOJGUlITIyEiYmJggJiYGHh4efIekskpLS7F+/XpkZmbi888/x1NPPcV3SIQQMt5Q1zVCCBkvAgICcPnyZVhZWcHb2xs7d+5EV1cX32GpFMYYvv/+e8yYMQNVVVW4cOECFTmEEMITKnQIIWQcsbGxwenTp7F9+3bs3LkTEydORHJyMt9hqYQrV67Az88PGzduxLp165CZmUlnzQghhEdU6BBCyDjS1dWFxMREFBQUQENDA8XFxQgICICfnx/OnTvHd3hjUmFhITZt2gQvLy/cvHkTX375JT799FO6DooQQnhGhQ4hhKi4zs5OxMfHY9OmTbCzs8Py5cuRnp6OLVu2IC8vD8nJyRAIBFi4cCGWLl2Kn3/+me+Qx4Rr165hw4YNcHV1xfnz5/H666/D0tISmzZtgpOTE15++WVcunSJ7zAJIWTcosEICCFEBXV0dCApKQkSiQRHjhxBU1MT3N3dIRaLsWHDBkycOLHPe5KTk/Hmm2/i/PnzmDp1KjZv3ozHHnsMBgYGPHwC5dTZ2YlDhw7hq6++QkpKCtzd3bF161asWbMGamp/HDvMyclBTEwM9u/fj/z8fDg6OiIkJARRUVHw8vLi+RMQQsi4kU6FDiGEqIj29nYkJydDIpEgLi4Ora2t8PHxgVgsRkREBGxtbQe1nKysLHz11VfYt28f1NTUsGrVKqxevRoBAQEQiUQj/CmUj0wmQ1paGg4cOIADBw6gvr4eK1euxObNm+Hv7w+BQHDf92ZnZ0MikWDPnj0oLCzkis3169fD1dV1FD8FIYSMO1ToEELIWNbW1obTp09DIpEgNjYW7e3tmDt3LsRiMVavXg1ra+thL7uhoQF79uzBTz/9hIyMDBgbGyMsLAwhISFYuHChSl+D0tnZibS0NBw9ehQHDx5ESUkJpk6dirVr1+Kpp56CjY3NkJeZlZWF6OhoxMTEoKKigit6oqKi4OLiMgKfghBCxjUqdAghZKypr69HfHw8JBIJkpKS0N3dzRU3a9euhaWlpcLbvHPnDiQSCWJiYnDp0iWIRCL4+PhgyZIl8Pf3x6xZs8b02R6ZTIacnBycPn0aiYmJ+Pnnn9Ha2go3NzdERERgzZo1mDp1qsLaSktLg0Qiwb59+1BbW8udeVuzZg2srKwU0g4hhIxzVOgQQshYUFdXh2PHjkEikSAxMRECgQABAQEIDg5GWFgYzM3NRy2WqqoqJCUlITExEUlJSSgvL4eWlha8vLzg7e2NuXPnYtasWXB2duauW1E2xcXFuHLlCjIyMpCeno7MzEw0NTXB2NgYixYtwpIlS7BkyRI4OTmNaBydnZ1ITEzkuhu2tbVxRWtkZCTMzMxGtH1CCFFhVOgQQoiyqqmpQUJCAiQSCU6dOgV1dXUsXrwYYrEYoaGhSjFIAGMMeXl5yMjIwIULF3DhwgVcv34dUqkU2tracHNzg5ubG6ZOnQoXFxfY29vDyckJVlZWI14EVVZWori4GMXFxSgsLERubi6uX7+OvLw8NDY2AgBcXV0xd+5ceHt7w8fHBx4eHlBXVx/RuO6n5wAShw4dglQqRUBAAMRiMcLCwqCvr89LXIQQMkZRoUMIIcqkpKQECQkJiI+Px8mTJyESieDv7z+mdnZbWlqQk5OD69evIzc3F9nZ2cjNzUVxcTGkUikAQCQSwdbWFlZWVjA2NoaJiQlMTExgbGwMHR0d6OvrQygUAgD3O2MMDQ0NAMD93tnZibq6OtTX16Ourg51dXWoqalBUVEROjo6AAACgQA2NjaYMmUK3N3d4e7uDjc3N0yfPh0mJib8rKQHaGxsxJEjR7giVygUcttBREQEdHR0+A6REEKUHRU6hBDCt6KiIsTGxkIikSAtLQ3a2tpYtGgRxGIxwsPDVeai/+7ubpSVlaGoqAh37txBcXExqqurexUq9fX1aGtrQ2trK7q6ugAATU1NXIFkbGwMAFBXV4eBgQE0NTW5AkleMJmamsLR0RH29vZwcHCAnZ0dNDU1efvcD6tnt8WTJ09CT08PwcHBEIvFWLZs2Zi+NooQQkYQFTqEEMKH27dvc0fs09LSYGRkhKCgIIjFYixZsmRM75grWnV1NSwsLHDmzBksXLiQ73B4VVpaioMHD3LbjbGxMVasWAGxWIzly5fz1u2OEEKUEBU6hBAyWgoLC7nR0lJTU2FiYsLtpC5duhQaGhp8h6iUqNDpX88zgampqbC1tcWqVasgFovh6+s74P19CCFkHKBChxBCRpL8hpESiQQ5OTkwMzNDYGAgdTsaAip0HiwnJwcxMTHYv38/8vPz4ejoiJCQEERFRcHLy4vv8AghhA9U6BBCiKLJi5sDBw4gLy8PdnZ2WL58OYKCghAYGMhdZE8GhwqdoZFvf3v27EFhYSF3Y9L169fD1dWV7/AIIWS0UKFDCCGKIN+53LdvH27cuAEHBweEhoZSNyIFoEJn+LKyshAdHY2YmBhUVFRwRU9UVBRcXFz4Do8QQkYSFTqEEDIc8rvbHzt2DAcPHsStW7fg5OSElStXUnGjYFToPDz59iovxmtra+Hj4wOxWIw1a9bAysqK7xAJIUTRqNAhhJDB6rmzePDgQZSVlcHFxYUbLc3Pz4/vEFUSFTqK1dnZicTEREgkEsTFxaGtrQ1z586FWCxGZGQkzMzM+A6REEIUgQodQggZiFQqRXp6OiQSSZ/uP6tXr4a7uzvfIao8KnRGTkdHB5KSkiCRSHDo0CFIpVIEBASMqRvUEkLIfVChQwgh9+pZ3Ozfvx9VVVVccbN27VpMmTKF7xDHFSp0RkdjYyN3b6dTp05BKBTC398fYrEYERER0NHR4TtEQggZCip0CCEE6H1k++jRo2hsbOSKm8jISEyaNInvEMctKnRGX11dHY4dOwaJRIKTJ09CT08PwcHBNCw6IWQsoUKHEDJ+tbe3Izk5GRKJBEeOHEFLSwt8fHwQHByMiIgITJgwge8QCajQ4VtpaSkOHjwIiUSCtLQ0GBsbcze6Xb58OdTV1fkOkRBC+kOFDiFkfGlra8Pp06chkUgQGxuLtrY2bvQpsVgMGxsbvkMk96BCR3kUFRUhNjYWEokEqampsLW1xapVq2ikQUKIMqJChxCi+hoaGpCUlIT4+HgcPnwYHR0d3ChTNLSu8qNCRznl5OQgJiYG+/fvR35+PhwdHRESEoKoqCh4eXnxHR4hhFChQwhRTT2vMUhMTIRUKuWKm3Xr1sHCwoLvEMkgUaGj/OQ3zN2zZw8KCwu569vWr18PV1dXvsMjhIxPVOgQQlRHbW0tjh8/zo0apa6ujsWLF0MsFiMkJASGhoZ8h0iGgQqdsSUrKwvR0dF9hmOPioqCi4sL3+ERQsYPKnQIIWNbdXU1Tpw4wY0OJRKJuCFxQ0NDYWBgwHeI5CFRoTM29bzB7r59+1BbW8tdD0ddRgkho4AKHULI2FNcXIzDhw9DIpEgPT0dWlpaWLRoEcRiMcLDw6Gnp8d3iESBqNAZ+zo7O5GYmAiJRIK4uDi0tbVxXUkjIyNhZmbGd4iEENVDhQ4hZGy4c+cO4uLiuCFuDQ0NERwcjODgYCxfvhy6urp8h0hGCBU6qqXnPasOHToEqVSKgIAAiMVihIWFQV9fn+8QCSGqgQodQojy+v3333H06NF+79+xdOlSaGho8B0iGQVU6KiuxsZGHDlyhLuuTigUcl1PIyIioKOjw3eIhJCxiwodQohyyc7OxrFjxxAfH4/U1FSYmppi+fLldEf2cYwKnfGh50iJJ0+ehK6uLlauXEnffULIcFGhQwjhn3xo2piYGOTm5sLc3BzLli2DWCxGYGAghEIh3yESHlGhM/6Ulpbi4MGD/Z7NXb58OdTV1fkOkRCi/KjQIYTwQ17cyG82aG9vj7CwMIjFYjz66KNQU1PjO0SiJKjQGd+KiooQGxsLiUSC1NRU2NraYtWqVRCLxfD19YVAIOA7REKIcqJChxAyeuTFzd69e1FQUMDdSZ12WMhAqNAhcjk5OYiJieEOkMhzSFRUFLy8vPgOjxCiXKjQIYSMnJ730Th06BBKS0vh7OyM4OBgKm7IoFGhQ/ojP3CyZ88eFBYWcjcmXb9+PVxdXfkOjxDCPyp0CCGKJZVKkZ6eDolEAolEgvLycri7uyM4OBhBQUHw8/PjO0QyxlChQx4kKysL0dHRiImJQUVFBVf0REVFwcXFhe/wCCH8oEKHEPLwehY3Bw4cQGVlJbejsWbNGri5ufEdIhnDqNAhg9XzLPK+fftQW1sLHx8fLhdZWVnxHSIhZPRQoUMIGR75nc6PHTuG2NhYVFdXc8XNunXrMHnyZL5DJCqCCh0yHPIcJZFIEBcXh7a2NsydOxdisRiRkZEwMzPjO0RCyMiiQocQMng972h+9OhRNDY2csXNhg0bMHHiRL5DJCqICh3ysHrmrkOHDkEqlSIgIABisRhhYWHQ19fnO0RCiOJRoUMIGVh7ezuSk5O5o6Ktra1cV5CIiAjY2tryHSJRcVToEEVqbGzEkSNHIJFIcOrUKQiFQvj7+3M5TUdHh+8QCSGKQYUOIaSvtrY2nD59GhKJBLGxsWhvb+e6fKxevRrW1tZ8h0jGESp0yEipq6vDsWPHIJFIcPLkSejq6mLlypUQi8VYtmwZRCIR3yESQoaPCh1CyB/q6+sRHx8PiUSCpKQkdHd3c8XN2rVrYWlpyXeIZJyiQoeMhtLSUhw8eBASiQRpaWkwNjbGihUrIBaLsXz5cqirq/MdIiFkaKjQIWQ863k0MzExEQKBAAEBAQgODkZYWBjMzc35DpEQKnTIqCsqKkJsbCwkEglSU1Nha2uLVatW0f2/CBlbqNAhZLypqalBQkIC1z9dXV0dixcvhlgsRmhoKAwMDPgOkZBeqNAhfMrJyUFMTAz279+P/Px8ODo6IiQkBFFRUfDy8uI7PELI/VGhQ8h4UFJSgoSEBMTHx+PkyZMQiUTcxbc04hBRNsHBwbh9+zb3XCqV4tatW7Czs+t1obiWlhbOnDlD2y8ZNdnZ2ZBIJNizZw8KCwu5USfXr18PV1dXvsMjhPRGhQ4hqqpn14u0tDRoa2tj0aJFEIvFCA8Ph56eHt8hEtKvl156CZ9++ikG+vckEAiwYMECnD17dhQjI+T/ZGVlITo6GjExMaioqOCKnqioKLi4uPAdHiGECh1CVMvt27e5YVPT0tJgZGSEoKAgiMViLFmyBJqamnyHSMgDZWZmYs6cOQPOo66ujt27d+PJJ58cpagI6Z9MJkNaWhokEgn27duH2tpabgj+NWvWwMrKiu8QCRmvqNAhZKwrLCzkRktLTU2FiYkJN1LQ0qVLoaGhwXeIhAyZs7Nzr+5r9xKJRKiqqoKRkdHoBUXIA3R2diIxMZG771hbWxs3emVkZCTMzMz4DpGQ8YQKHULGInk/cYlEgpycHJiZmSEwMJDu/UBUxrZt2/Duu+/i7t27fV4TCoVYsWIF4uLieIiMkMHp6OhAUlISJBIJDh06BKlUioCAALo2kpDRQ4UOIWOFvLg5cOAA8vLyYGdnh+XLlyMoKAiBgYEQCoV8h0iIwuTl5cHNza3f19TU1HDgwAFERESMclSEDE9jYyPXrfjUqVMQCoXcgDARERG9BtkghCgMFTqEKDN5cbNv3z7cuHEDDg4OCA0NpXs5kHFh2rRpyMnJ6TMogba2Nmpra6Gtrc1TZIQMX8/7l508eRK6urpYuXIlnZEnRPHS1fiOgBBVdPnyZezbt2/I75PJZEhJScFf//pXTJw4EdOmTcOPP/6IZcuW4fz587h9+zY+++wz+Pn5UZFDVF5UVFSfu9GLRCJERERQkUPGLBMTE0RFRSE+Ph63b9/GP/7xDxQWFiIkJARWVlbca1KpdMjL3rVr17DeR4iqojM6hCjYt99+i82bN2PatGn49ddfHzh/zxF7Dh48iLKyMri4uHCjpfn5+Y1C1IQon+LiYjg6OvY5o3PixAksW7aMp6gIGRk9bwmQmpoKW1tbrFq1atBn8LOzszFt2jQsWrQIMTExMDU1HaXICVFa1HWNEEVpa2vD5s2bER0dzf1Dun37NhwcHPrMK5VKkZ6eDolE0uceDKtXr4a7u/toh0+IUnr00UeRkZEBmUwGADAyMkJ1dTVdk0ZUWk5ODmJiYrB//37k5+fD0dERISEhiIqKgpeXV7/v+fvf/4733nsPjDFYWloiPj4eM2fOHOXICVEq1HWNEEW4efMmZs+ejb179wIAGGMQCoU4dOgQN49UKkVKSgqef/552NjYYN68eUhOTsamTZuQm5uL7Oxs7Nixg4ocQnp47LHHuAMHIpEIkZGRVOQQlefu7o4dO3YgLy8P169fxxNPPIFjx45h9uzZmDp1Knbs2IEbN270es+ePXtw9+5ddHd3o7KyEt7e3vjuu+94+gSEKAc6o0PIQzpy5Ag2bNiAzs7OXkPhCgQCeHl5Ydu2bZBIJDh69CgaGxu5MzeRkZGYNGkSj5ETovxqampgZWXFXXeQkpICX19fnqMihB9ZWVmIjo7u0+vJdG0AACAASURBVBNgxowZCAsL6/c9GzduxOeff073VCPjEXVdI2S4uru7sXXrVrz//vsA0Oc6Ajk1NTX4+PggODgYERERmDBhwmiGSciYt2zZMpw6dQo2NjYoKSmhgTjIuNfz2s59+/bBwcEB165dQ1dXV595hUIhZs2ahbi4OFhbW/MQLSG8oUJnPGhubkZ3dze6u7vR3Nzcaxrwxw56Q0PDfd//oNcBQEtLa8BRkO59XV9fH0KhEEKhkLtpmnzaWFBSUoJVq1YhKytrwBFuhEIhduzYgb/97W+jGB0hyqG9vR0dHR29ck99fT33eltbGzo7O/t9b1dXF1pbWwEA58+fx7///W+EhoZi/fr1AAbOOT3zCgAYGhpCTU0NOjo60NTUhEgkgp6enkI+IyF86+jogJOTEyorK+87j3ybP3ToEBYuXDiK0Q1PR0cH2tvb75s7Wlpa+r2ZMDBwXgH+Lx/0p+d+iDzH9MwnxsbGw/o8hDdU6CgDmUyG+vp6NDQ0oKGhAfX19WhpaUF7ezuam5vR3NyM9vZ2tLS0oKmpCe3t7WhtbUVjYyPa29vR1taGxsZGyGSyfouasaa/4kdHRwfa2towNDSErq4utLW1YWBgAD09PWhra0NfX7/X7/LnxsbGMDIygrGxscJuyHbmzBmIxWI0NzffN9HKyc/mpKSkKKRtQkZKU1MT6urquEdTUxNaW1u5XHPv8+bmZu55Q0MDZDIZGhsbuWWNlSFu5Tsu8lyjr68PXV1d6OrqwsjIqM9zPT097rmxsTEMDAxgYmLCPQgZbb/88gsWLFjwwPnU1dXBGMPOnTvx2muvKaz9rq4u1NXVoba2lssdLS0tXJ5oaWnh9l/kr7W2tqKpqYnbd2loaABjbMACRpnI84W2tja0tLS4nCDf75D/rqenx+UNee4wMjKCkZERTE1NYWpqCgMDA74/jiqjQkeRpFIpampqUF1djZqaGlRUVKC6uporYu79Kf+9qamp3+XJj8Do6+tDW1u7z+8GBgbQ0tLifldXV+eOWKqpqcHQ0BAABpwmp6enN+BNyh70es8zRIN5XZ7cOjs70dbW9sBpLS0t6Ojo4JKkvAi83+/90dDQ4IoeeaLpWQgZGxvDzMwM5ubmMDMzg6WlJSwsLKCrqwvgjzNb77//Pl5//XUIBAJuFKgHEQgEKC0tpS4DZNS0traioqKCy0FlZWXcTsj9Hv19f+X/wA0NDQfc4TcyMoJAIOB+yvOFpqYmdHR0euWenkdTBzqzIl+e3I4dO7Bjxw7u+UA5R34mSU5+JFi+EyXPMVKplMu/8lzT2NjIFXBNTU19Cjr5Tpr8bNO9Mfcseu59mJqawtLSEtbW1jA3N4eNjQ3t5JCH9pe//AXffPNNv93W+iMQCLB27Vp88803/R4ArKurQ0VFBaqqqlBaWsrt08hzRW1tLZdPamtr0dLS0mcZ8u+8/KBjzzxy73OhUNhnH0ZDQwO6urr3zR3y3NKfgfJKz+/8ve7tvSI/MyQ/u9zzYM69hZm8mOuZJ/p7Lt+36UkoFHL54d58YWJiAgsLC1hZWcHS0hI2NjawsLCg660GjwqdB2GMobKyEqWlpSgtLUVJSQmqqqpQXV2NyspKVFdXc0mgpqam13UaampqMDc3h4mJyYA72P29Npa6cSkjeeFzb1E5UMFZV1eHmpqaPjsw2traMDExQXNzM5cg5cmWMXbfa3N6+uqrr/Dss88q/oOScaW9vR137txBUVERiouLUVpaiqqqKpSVlaGqqgqVlZUoLy/vsw2bmZnBzMxswJ3wex/yHQ9l0d3drXQ5UX6gqufR7IEe8gNhPQ+SaGlpccWPfIdG/nBwcICjoyMcHByoICL96u7uhoWFRa9uXYNlYWGBxYsXo6WlBZWVlVwe6dntSyQScQf/eu5898wnPXfQTU1NYWhoSDf0vQ95MdXQ0NCneLzf75WVlX2KM/lBk57Fj42NDezs7ODg4AAnJydYW1vT9YzjvdBhjKGsrAy///47SkpKUFZWhuLiYpSVlaG0tBTFxcUoLy/vdRpVvnGZm5vDwsICFhYWfc4AyHcqzM3NaSMbg9ra2lBTU8MVspWVlTh69CjKysrQ1tbGdSVsa2tDW1tbr50WkUgEDQ0NaGpqgjEGkUgEkUiEadOm4cMPP4SLi4vCutAR1VNXV4eCggKumCkqKsKdO3dQXFyM4uJiVFdXc/Pq6urC3t6+186x/J+dubk5rKysuDMHdPRPeUilUu5gmfxoeUVFBcrLy7mzb/Ij6T2PLhsaGvYqfOzt7bnnEydOhKWlJY+fivAlIyMDa9asQXd3NxhjXNd1qVTKPbq7uyGTyXp1J1VXV4dIJIKOjg7mzZsHFxcX2NjYwNLSkssd8n0cwr/29nYuT8gPblVWVnJn7uUH5CsqKrgz3RoaGlyekBc/8pwxYcIEODg43PdaJRWi+oVOR0cHysrKUFhY2OeRl5fX68insbExXFxcYG1tDRsbG+6nfJq9vT0dVSN91NfXo6ysDOXl5dzPwsLCPtPk5NtZfw8HBwelO2pNFKu+vr7ffCR/yPXcTnrmIvlzOlqn+nr+/+qZW+TPb9++zXWF0dTUhK2tLdzd3TF16tReecXZ2Zm2FRXR1dWFkpISFBYWIjs7Gzk5Odw28fvvv3M9DB6UP2xsbHj+JGSk3Ps/pmfuKCgo4LrfaWhowM7ODi4uLn3yhpOTk6oUQapR6HR3d6OwsBA5OTnIy8tDbm4ucnNzUVBQwJ3OVVNTg62tLZydneHs7Mwlf/nvVlZWqvJHJUqotbUVt2/f5v4Z/f77771+l/dxFgqFcHBwwOTJk+Hu7o4pU6bAzc0Nbm5udKHzGNPQ0IBr167h+vXruHr1Kq5fv47c3FzU1tYC+OPsn7OzMyZOnAhXV1dMnDgRkyZNwsSJE2Fvbz/gNXGEAH8MZFNWVoabN2+ioKCgz0/5dUoGBgZwdXWFh4cHpk2bhunTp2P69Ol0FkiJ3b17F/n5+bh27RquXr2Kq1evIjs7G0VFRWCMQU1NDQ4ODpg0aRL3cHV1xaRJk+Do6Ehnccl91dTUcHnixo0buHnzJveQX+Osp6eHKVOmwMPDg8sXnp6eMDMz4zn6IRtbhU53dzfy8vJw9erVXkVNQUEBurq6IBAI4OjoyO0kTpo0iStoHB0de118T4gyqaqq4oof+dnG3Nxc5Ofnc0WQubk5pk6dismTJ8PNzQ3u7u6YOXPmWEw8KkUmkyEvLw+//vorrl27xhU3xcXFAP7ociTfuZTnJfnOCJ29IyOFMYbi4mJuhyY3N5cruuVdIM3NzbmdmGnTpmHmzJnw8PCgInuUNTQ0IDMzE5cvX+ZySE5ODrq6uiASieDm5sb9nVxdXbkDI7RPQxStvLycK35yc3O5IruqqgoAYG1tjenTp3MFkJeXF9zc3JT5RIHyFjp3797FjRs3kJWVxT1+++03tLW1QSQSwd7evtepNnd3d8yYMYPujUBUTn19PddF4d6uCsAficfLywtTp06Fu7s7vLy84O7uTl1VRkhZWVmvvJSWloa6ujqIRCJMmjSJ+zvIf9Lfgiibe3NKVlYWrly5gpaWFm479vPzg6+vL+UTBevu7kZ+fj6ysrKQmpqKlJQU5OXlQSaTwdjYmMvh8vwxe/ZsaGlp8R02GefkOSMrK4vLG/J9cj09PXh6esLLywt+fn6YN28erKys+A5ZTjkKHZlMhuvXr+P8+fO4cOECfvvtN+Tl5UEqlcLAwAAzZszAzJkzMXPmTMyYMQPu7u50xImMe1VVVfjtt996PQoKCsAYg4mJCWbNmoXZs2fD19cXvr6+dKOzYZDJZLhy5QrOnDmDlJQUXLx4EWVlZVBTU4ObmxseeeQRzJkzB3PmzKEj4WRMk5+ZvHjxIve4evUq7t69CxMTE8yZMwc+Pj5YtGgRvL29aVsfpNbWVvzyyy84c+YM0tPT8euvv6K9vR2GhoaYM2cOvL29uZ904T8ZS7q7u3Ht2jVcuHABFy9eREZGBvLz8yGTyeDo6Ii5c+di/vz58Pf3x+TJk/kKk59Cp729HZmZmUhJSUFqaipSU1PR2NgIAwMD+Pj4YNasWVxhM2HCBDqSRMggNTc34/Lly1zhc/HiReTm5kIgEMDd3Z07Sjtv3jw4OjryHa5SysnJwdmzZ3HmzBmcO3cOdXV1MDc3x7x587idEi8vL+6mtoSoqo6ODly+fBkXL15EZmYmfv75ZxQXF0NXVxfz5s3DwoULsWjRIsycOVOphiLn0927d5GRkYHTp0/j9OnTyMjIQFdXF6ZOnYr58+dzRc3kyZOVubsPIcPS2NjIFT0XLlzAL7/8gubmZtjZ2cHf3597jOJgGKNT6MhkMmRlZeHEiRM4deoULl26hK6uLtja2mLevHncjte0adMoWRKiYLW1tVwXidTUVO77Z2dnh4ULFyIwMBBLliyBqakp36Hyoq2tDYmJiYiLi8OpU6dQUVEBQ0NDzJ8/H4sWLcKiRYswffp0OuBCCICCggKcOXMGZ8+exdmzZ1FZWQkjIyMsWrQIoaGhCAoKGndnj2tra3H06FHExsbi7NmzaGlpgYODQ68dOyXqykPIqOnu7sbFixe5wv/ChQvo7OyEm5sbgoKCEB4eDm9v75H8/zpyhU5dXR0SExNx4sQJnDx5ElVVVbCzs0NgYCDmz58PPz8/ODk5jUTThJAB9DyjmpycjJSUFMhkMsyZMwcrVqxAYGAgZs6cqdI79nV1dTh27BhX3HR0dMDHxwdBQUFYtGgRvLy86KALIQ/AGENOTg7OnDmDhIQEnDlzBowxLFiwAKGhoQgNDYWtrS3fYY6IsrIyxMXF4fDhw/j5558hFAoREBCA5cuXw9/fH5MmTeI7REKUTltbG86fP4/k5GTExcWhoKAAdnZ2CAsLQ3h4OObNm6fo/72KLXTq6upw4MAB7Nu3D2lpaRAIBPDz88OyZcsQGBgIDw8PRTVFCFGQpqYmJCUlcQclSktLYWVlhbCwMGzYsAE+Pj4qUfR0dXXhyJEj+Pbbb3H69Gmoqalh0aJFCAsLQ0hICA21S8hDamxsREJCAuLi4nDixAm0tLTA29sbTz75JNatWzfmu3t2dHTgwIED+Oabb5CWlgYdHR0EBgZi1apVWL58+Zj/fISMtitXruDw4cM4fPgwrl+/DnNzc6xduxbPPvss3N3dFdHEwxc6nZ2dSEhIQHR0NBISEiASiRAWFobQ0FAsXrwYhoaGigiUEDIKGGO4cuUKEhISsG/fPly/fh0TJ05EZGQkNmzYgIkTJ/Id4pDdunULu3fvxg8//ICamhoEBgZi/fr1WL58OeUnQkZIR0cHkpOTceDAARw8eBAikQjr1q3DM888Ay8vL77DG5IbN27g66+/xg8//IDm5maEhYVh/fr1WLJkCbS1tfkOjxCVcOPGDRw8eBDfffcdbt26hfnz5+PZZ59FeHj4wwylng42TPn5+ey5555jJiYmTE1NjS1evJj9+OOPrLm5ebiLJIQomd9++4299NJLzNramgkEAvboo4+yPXv2sM7OTr5De6BTp06xxYsXM4FAwOzs7Nj27dtZUVER32ERMu7U1tayTz/9lLm7uzMAzMvLi+3du5d1d3fzHdqAkpKSmL+/PxMIBMzR0ZG9/fbbrLy8nO+wCFFpUqmUnTx5koWGhjKhUMgsLCzY9u3bWVNT03AWlzbkQufChQssODiYqampsQkTJrD33nuPlZSUDKfxEbVv3z4GgAFgmpqafIejEj744ANundra2vIdjlJTtXXV3d3NTpw4wdasWcNEIhGzsbFh7733HmtpaeE7tD6SkpLYnDlzGAC2ZMkSdvToUaXZoaK8pHiq9l1TdefPn2fr1q1j6urqbNKkSWzv3r1MJpPxHVYvv/zyC3v00UcZABYQEMCOHTvGpFIp32ExxiiHjITxmkMGsy3t37+feXp6Mi0tLW7ea9eujVqMJSUlbNu2bczIyIiZm5uzTz/9dKgHWgdf6GRnZ7Pg4GAGgD366KMsNjZWab74A/H39+/zB2xubmYTJ05kK1as4Cmqsc3T03NcJYOHoYrrqqioiL366qtMX1+fWVpasn/961/s7t27fIfFCgsLuRwVFBTELl68yHdI90V5SfFU8bumym7evMmefPJJpq6uzry9vVlmZibfIbHy8nK2du1aJhAIWEBAAEtLS+M7pPuiHKJ44zWH9LctMcZYSkoKEwgE7JVXXmHNzc2soKCA2dnZjWqhI1dbW8teffVVpq2tzZycnNj+/fsH+9a0Bw7i3tHRgTfeeAMzZsxASUkJEhISkJqaitDQ0DE7BjxjDDKZDDKZbNjL0NPTg5+fnwKjImRssLe3x3vvvYdbt25hw4YNeOWVVzB79mxkZGTwFtOuXbvg4eGB33//HcnJyYiPj8cjjzzCWzzDQXmJjCcTJ07Ed999h6ysLGhra8PHxwdbt27F3bt3eYnn0KFDmDZtGjIzM3HkyBEkJibCx8eHl1iGi3IIUSSJRALGGJ5//nno6elhwoQJKC4uxrRp00Y9FhMTE7z33nu4ceMG/P39sW7dOojFYtTV1T3wvQNWKoWFhfD19cWXX36Jjz/+GJmZmQgMDFRY4HzR19fHrVu3kJCQwHcohIxZ5ubm+PDDD3H16lXuhpofffQR2Cjeg7irqwtPPfUUnnvuOWzZsgVZWVnw9/cftfYVifISGY88PT1x5swZ/Otf/8Knn36KwMBA1NfXj1r7jDFs374dYrEYERERuHz5MoKDg0etfUWiHEIUqbi4GACU6h57dnZ2+Oabb5CUlISMjAzMnTsXN2/eHPA99y10Ll++jLlz50Imk+HSpUt47rnn6L4ShJA+XF1dkZiYiA8++ACvv/46Nm3a9FBHFAdLKpUiMjISEokEhw8fxttvvw0NDY0Rb5cQolgCgQCbN29GWloaCgoKsGDBglErdl599VXs3LkTX3zxBXbt2gU9Pb1RaZcQZSeVSvkO4b78/f3x66+/wtLSEr6+vsjPz7/vvP0WOsXFxQgICICHhwdSUlLGxJCyeXl5CA0NhaGhIXR1dTFv3jykpKT0mS8uLg4CgYB7dHR0cK91dnZi27ZtmDJlCnR0dGBiYoLg4GAcPXqU+4N/+OGHEAgEaG1tRWpqKrccoVDILae7uxsHDhxAQEAArKysoK2tjenTp+Ozzz7rtQN4byy3b9/GmjVrYGRkBFNTUwQFBeHWrVt9PkNtbS1eeuklTJgwAZqamrCzs8PixYvxww8/oL29vde81dXV2LJlC5ycnKChoQFzc3OEh4fj8uXLClnnK1asgKGhIXR0dLBw4UKkpqYCABoaGnp9NoFAgH/+85/c+uk5PSIiYtBtDnWd/fOf/+Tm7Xk6/uTJk9x0MzOz+y7/zp07WLNmDfT19WFqaorHHnsM9fX1uH37NoKDg6Gvrw9ra2ts3LgRzc3Nw1pXcoPdbpSRQCDA888/j4MHDyI6OhpvvPHGiLf5//7f/8OJEyeQmJiIlStXjnh7w0F5aXzkpaGsg6HmJPnfViAQwM7ODpmZmfD394e+vv6of76R5uHhgZ9//hn19fVYs2bNiJ8d3r17Nz7++GPs2bMHmzdvHtG2hotyCOWQh80hPT/XULalI0eOAAC0tbUhEAgwd+7cIX+ekWRmZoYTJ07AyckJK1euRFtbW/8z9nfljr+/P5s6dSprbW1VwCVEI+/mzZvMyMiI2drassTERNbc3MyuXr3KlixZwpycnPq9yCokJIQBYO3t7dy0p59+mhkaGrLExETW1tbGKioq2Msvv8wAsLNnz/Z6v66uLvP19e03nvj4eAaA7dy5k9XV1bHq6mr2r3/9i6mpqbGXX375vrGEhISwtLQ01tLSwpKSkpi2tjZ75JFHes1bXl7OnJ2dmZWVFYuPj2dNTU2soqKCvfXWWwwA++STT7h5y8rKmKOjI7O0tGTHjx9nzc3N7Pr162zBggVMS0tr2Bdaenp6MkNDQ7Zw4UKWkpLCmpubWWZmJvPw8GAaGhrs3Llz3LxLly5lampqrKCgoM9yfHx82N69e4cVw1DWGWP3/3t5eXkxU1PT+y4/PDycXbp0ibW0tLDo6GgGgAUGBrKQkBD222+/sebmZrZr1y4GgL344ot9ljOUdTXU7UZZ/fDDD0xNTY39/PPPI9bGhQsXmJqaGvvvf/87Ym08LMpL4ysvDWUdMDb0nOTp6cl0dXWZj48P9/cY7bw7Wi5evMiEQiH7/vvvR6yNqqoqZmhoyF577bURa+NhUQ6hHKKoHKKobUkZlZaWMiMjI/bGG2/093LfUdcuXrzIALDz58+PfHQKIhaLGQB28ODBXtNLS0uZpqbmoP+Azs7O7NFHH+0zr6ur65CTwZ/+9Kc+0zds2MBEIhFrbGzsN5b4+Phe0yMiIhgAVl1dzU174oknGAB24MCBPstftmxZry/C448/zgD0+cKVl5czTU1N5uXl1W/8D+Lp6ckAsPT09F7Tr169ygAwT09PbtqpU6cYAPbnP/+517wpKSnM1taWdXV1DSuGoawzxoZf6Bw/frzX9KlTpzIAfXbinZ2d2eTJk/ssZyjraqjbjTLz9/dny5cvH7Hlr1+/nnl7e4/Y8hWB8tIfxkteGso6YGx4hQ4A9ttvv/WaPpp5dzQ99dRTvT6Ton344YfMyMhIqQ/oUg75A+WQh88hitqWlNXOnTuZqakp6+jouPelvoXOO++8w5ycnEYnMgXR19dnAPq9Wen06dMH/QfcvHkzA8A2btzI0tPTB7z3xkDJ4H7kY7Xfe7RBHktFRUWv6S+++CIDwK5cucJNMzQ0ZAAGdeMkQ0NDpqam1u8O8qxZsxgAVlxcPKTPwBjjxlTv794HNjY2DAArKyvjpk2fPp3p6OiwmpoablpISAh79913h9x2z/cPdp0xNvxCp7Kystf0gIAABqDPP0c/Pz+mr6/fZzlDXVf9ud92o8x++OEHpqWlNWJD0Nvb27MPPvhgRJatKJSX+qeqeWko64Cx4Z/R6c9o5d3RlJCQwACwhoaGEVl+aGgoW7t27YgsW1Eoh/SPcsgfhpJDFLUtKau8vDwGgP3666/3vtR3eOmamhpYWlreO1lpdXZ2orm5GVpaWv1eRGhhYTHoZX3xxReIjo5GYWEh/P39YWBggGXLliE2NnZIMTU2NmLbtm2YPn06jI2NuT6Tr7zyCgDctx+hoaFhr+fyC6vlfV87OzvR2NgILS0t6OvrDxiDfF6ZTAZDQ8M+fUp//fVXAHjgaBX3Y2pqCoFA0Ge6fH1XVVVx01544QW0tbXhyy+/BADcuHEDZ86cwTPPPDOstnt60Dp7WAYGBr2eq6mpQV1dHTo6Or2mq6ur37fNwa6r4W43ysjKygodHR1oaWkZkeXX1dX12wdZWVBe6p+q5qWhrIOHYWRk1O/00c67o8Hc3BwABjV87HBQDqEc0h9VzCGK3JaU1UD5ok+hM2HCBOTn5/e6kE2ZaWpqQl9f/747VUNJkgKBAI899hiSk5PR0NCAuLg4MMYQHh6Ojz/+uM+89xMcHIy33noLGzduxI0bNyCTycAYwyeffAIAw77AUlNTE4aGhujo6Bjwwnf5vEZGRhAKhbh79y4YY/0+Fi5cOKxYGhsb+50uTwI9vziRkZGwtLTE559/js7OTnz00Ud4/PHHYWxsPKy2h0NNTQ1dXV19pjc0NIx424NdVyO13fDh8uXLsLCw6FMoKoqTkxNycnJGZNmKQHnp/vOqYl4ayjqQG05Oqq2t7ffvpKx592FkZ2dDJBLB1tZ2RJbv6OiI3NzcEVm2IlAOuf+8lEP+MNgcoshtSVnJ9wecnZ37vNan0AkPD0d7ezu++eabkY9MQeT39jl58mSv6TU1NQMOOXcvIyMj5OXlAQBEIhECAgK40SeOHz/ea14dHZ1eG9jkyZPxn//8B1KpFKmpqbCyssKWLVtgbm7OJY57Rw0ZjrCwMADod5z8mTNn4sUXX+Seh4eHo7u7u8/oXgDw3nvvwcHBAd3d3cOKo6WlBVeuXOk17dq1aygrK4Onpyesra256Zqamvjzn/+MqqoqfPTRR9i7dy+ef/75YbU7XNbW1igtLe01raKiAkVFRSPe9mDW1UhvN6OppaUFX331FdavXz9ibYSHh2PPnj2D/ofAB8pLfxgveWko6wAYXk7q6OhAZmZmr2nKnHeHSyaTYdeuXQgODh6xIePDw8Nx7ty5IX0XRxvlkD9QDnn4HKKobUlZ7dq1C56ennBxcen7Yn993bZu3cp0dHRYVlaWIrrOjbiCggJmYmLSazSJ7OxstnTpUmZhYTHovoeGhoZswYIF7MqVK6yjo4NVVlayHTt2MADsn//8Z6/3L1u2jBkaGrKioiKWlpbGhEIhy8nJYYwxtmjRIgaAvf/++6y6upq1tbWxM2fOMAcHBwaAJSUlPTAWxhh77bXX+lx8Kh+Vw9ramh07dow1NTWx4uJitnnzZmZpacnu3LnDzVtZWckmTJjAXFxcWEJCAmtoaGC1tbVs165dTEdHp98L3gZD3lfcz8+PXbhwYcDRf+Sqq6uZtrY2EwgELCQkZFjt9jSUdcYYY8899xwDwP7973+z5uZmVlBQwFavXs1sbW0HvEbn3uUvXbqUqaur95l/wYIF/fafH8q6Gup2o4ykUilbv349s7CweOC1Rw+jqqqKmZmZsaeeemrE2nhYlJfGV14ayjpgbOg5ST4ilL+//wNHXRuJzzeaPvzwQyYSifrkcUW6e/cumzlzJps3b57SDs5AOYRyiKJyiKK2JWV07NgxJhAI2OHDh/t7ue9gBIz9kQCWLFnCTExM+ow+oazy8/NZaGgoMzAw4IYuPHbsGPP392cAGAD2P//zPyw2NpZ7Ln9ERkYyxhi7fPky27RpE3Nzc2M6OjrMxMSEzZ07l+3evbvPxWl5eXls3rx54OqMYQAAFHBJREFUTFdXl9nb27MvvviCe626uppt2rSJ2dvbM5FIxCwtLdkTTzzB/vrXv3Jtenl5sfT09D6x/O1vf2OMsT7TV6xYwS2/pqaGvfDCC8zZ2ZmJRCJmbW3N1q5dy27cuNFnvdTW1rKXXnqJubi4MJFIxMzNzdmSJUuGtdMsv+AQALO1tWUXL15kCxcuZHp6ekxbW5stWLCApaSk3Pf9Gzdu7HfEsqEY7jpraGhgTz/9NLO2tmba2trMz8+PZWZmMi8vL27+11577b7Lz8zM7DP9nXfeYefPn+8zffv27cNaV4PdbpRVZ2cne/zxx5mWltaoFGVHjhxh6urq3N9fGVFeGh95SW4o62CwOUnO09OT2draspycHLZ06VKmr68/6p9vNOzZs4epqamx999/f8TbunLlCtPX12dr165ld+/eHfH2hoNyCOUQReWQh9mWgL6j0SmD9PR0pqenx5544on7zdJ/ocMYY+3t7SwoKIhpaGiwzz77rN9RKAgZiu+++06pd9TJ8BUWFjJvb2+mp6fHTp48OWrtfvvtt0xdXZ09/vjjSn/UiSinsZKX5IXOUI2VzyeVStk//vEPJhAI2Kuvvjpq7SYnJzMdHR22ZMkSVl9fP2rtEtUxVr5jqiYmJobp6Oiw4ODggc7K3r/QYeyPxPPWW28xoVDIfH19+wzZS8hQzJkzR+lvVEeGprOzk73zzjtMR0eHeXh4sLy8vFGPISEhgRkaGjI3NzeWkZEx6u2TsW2s5KXhFjpj4fPdunWLzZ8/n4lEIvbVV1+NevuZmZnMxsaG2dvbj4kuwkS5jIXvmCppampiTz/9NAPAnnvuuQedje07vHRPampq2Lp1Ky5dugSZTIaZM2di9erVwx62j4wv33zzDcLCwtDS0oJdu3ahvr4eq1ev5jssogAymQwSiQTu7u5488038corr+DixYuYPHnyqMcSGBiI3NxcTJgwAT4+PoiKikJ5efmox0HGBlXPS2Pp87W2tmLHjh2YNm0a6urqkJ6ejmeffXbU45g9ezays7Mxf/58BAQEIDg4GLdv3x71OMjYMJa+Y6rk7t27+M9//oPJkyfjyJEjOHToEP79739DKBQO/MbBVlBSqZT99NNPbPLkyUwoFLLVq1ezCxcuPHxpRniHfvpi3vvYvn37kJe7e/duBoAJhULm4eEx4OAWIxUDUaympib28ccfMycnJyYSidjGjRtZUVER32Fxjh49ypydnZmuri7bsmULKykp4TskMkyUl/7Q8/oB+WOg69KG8vn40tzczD799FNmZWXFjI2N2bvvvtvfHc15kZSUxNzc3JiGhgZ75plnKIeMYZRDVINMJmMxMTFs4sSJ3Pfy3pu5DyBNwNjQBj7v7u6GRCLBxx9/jEuXLmHWrFl47LHHsG7dujF1o1FCyODIZDKcO3cOe/bswaFDh8AYw5NPPokXXnih/6Ecedbe3o6vv/4aH3zwAerq6hAREYGNGzdi/vz5fIdGyLh29epV7N69G//9738hk8nwv//7v3jxxRdhamrKd2i9dHZ2Yvfu3Xj33XdRW1sLsViMTZs2wdfXl+/QCBk3Ghoa8OOPP+Lrr79Gfn4+HnvsMbz55ptwcHAYymLSh1zo9JSSkoLvv/8ehw4dQmtrK5YsWYLIyEiEhv5/7d3bU1PnGgbwB0JIYhII0BwRBGsCCAFRBBStB6xWxzpI7yrjVW171b/H2stqp049tLWVsXJoi4rQAmpCOMshIQkJIZEcISTsi876dhKB7q1iAN/fzJokywUsnPDmfb5vHRpeunM8IWRzMRqNuHr1Kq5duwaLxYKqqio0NTXh4sWLm+Kmg6FQCNeuXcOVK1fQ3d2N4uJiXLp0CRcvXtzQd0QnZCvx+/24fv06vvnmGzx+/BharRafffYZLl26tOHrSCgUwrfffovLly+jt7cXer0eX375JZqamtbtZsiEvOu6urpw+fJlXL9+HTweD59++im++uorlJaWvsq3e72gwwkGg/j5559x9epV3Lt3D0KhECdPnsTp06dx+vRpaDSa1/0RhJB1xt2Arbm5Gb/++iuMRiMKCgpw4cIFNDU1obi4ONm7+MqePHmCK1eu4Nq1a1hYWMCpU6fQ0NCAc+fObbjRZEI2O7/fj+bmZvz444+4c+cOFhYWcP78eXz++ec4evQou9HkZtLd3Y2vv/4a33//PVJSUnD27Fl88sknOH36NCQSSbJ3j5BNzWAw4NatW7hx4waMRiMqKirwxRdf4MKFC687qPBmgk4sp9OJGzdu4JdffkF7eztCoRD27NnDQs+BAwfA4/He5I8khLwiu92O5uZmNDc34/79+/B4PNDpdDhz5gwaGxtx6NChTdmUrMbv9+OHH37AzZs30dLSgnA4jA8++ADnz59HQ0MD8vLykr2LhGxKLpcLd+7cwe3bt3H//n0sLi6irq4OjY2NuHDhwpaZRfV4PPjuu+9w48YN/Pnnn0hPT8epU6fQ2NiIjz/+GDKZLNm7SMim8Pfff+PmzZu4desWhoeHodFo0NDQgKamJhw4cOBN/Zg3H3RiBYNB/P7777h79y6am5sxNjYGmUyGQ4cOoa6uDocOHUJVVRWEQuF67QIhJMbU1BQ6Ojrw6NEjdHR0wGg0QigU4siRIzhz5gzOnDmD999/P9m7+VZ4vV40Nzfj9u3buHv3LrxeL8rLy3H8+HEcO3YMR44cocNTCFlFKBTCo0eP0N7ejra2NnR1dYHP56O+vh7nz5/HuXPnIJfLk72b68rpdOKnn37CrVu30NraCgA4cOAA6uvrUV9fj+rq6n+/IhQh7wibzYbW1la0tbWhpaUFZrMZhYWFaGxsRGNjI2pra5GauubFoF/F+gadRMPDw7h37x4ePHiABw8ewGq1QiAQoKqqioWfgwcP0qEkhLwBkUgERqMRDx48wMOHD9HR0QGLxQI+n4+qqiocPHgQx48fx9GjR9/5c+oWFhbQ3t6Oe/fuoa2tDQaDAampqaiqqsKxY8dw/Phx1NXVvfP/T+TdFQ6H0d3djba2NrS3t6OzsxOhUAi7du3CsWPH8OGHH+Kjjz6CVCpN9q4mhcfjwd27d/Hbb7+htbUVFosFUqkUR44cYcGnrKxsS82QE7IWj8eDP/74A62trWhtbYXJZEJ6ejpqa2tRX1+Ps2fPYu/eveu9G2836CSyWq14+PAha8T6+voQjUahVquxb98+tpSWlm7IqzsRslEsLS1haGgIPT09bHny5An8fj8kEglqa2vZLGpdXR1EIlGyd3lDm52dRWdnJx4+fIiWlhb09vYiNTUVRUVFcbWpuroa6enpyd5dQt44q9WKnp4e9hnd29uLYDAIlUqFw4cP48SJEzh58iQKCgqSvasb0vPnz9HS0oKWlha0tbXB5XJBKpWivLyc1eGamhooFIpk7yohry0SiWBwcJD1H7E9/c6dO3HixAmcOHECp06dettHSiQ36CRyuVzo6upCb28v+vr60NfXh/HxcQCAUqlEZWUl9u7diz179qC4uBg6nQ4CgSDJe03I22W32zEwMID+/n72t9Lf349wOAyxWIyKigpUVlaisrIS+/fvR1lZ2XpMB79TrFYrOjo60N3dje7ubvT29iIQCEAikWDfvn3Yv38/9u/fD71eD61WS4erkE1jeXkZ4+PjMBqN6OnpYe/xubk58Pl8VFRUoLq6GtXV1Th48CC0Wm2yd3nTiUaj6OvrQ2dnJ7q6utDV1cVuvK7ValFdXY2amhpUVlZCr9cjMzMzyXtMyOqi0SjGxsbw7Nkz/PXXX3j8+DF6enrg8/kgkUhQVVWF2tpa1NTU4PDhw8k+SmtjBZ2VeDweFnq4pm5oaAiRSAQ8Hg+FhYUoKSlBcXExiouLsXv3bhQXF9MJgWRTi0QiGB8fx8DAAAYGBjA0NASTyYTBwUF4PB4AQHZ2Ngs03KLT6ehiH2/B0tIS+vv70dXVxRpDk8mESCQCgUCA3bt3o7S0FHq9Hnq9HmVlZXShA5J0DocDBoMBRqMRRqMRBoMB/f398Pl8AIBdu3axUFNdXY3Kyko6h3adcAO73d3d7HFubg4AUFBQgLKyMuj1elRUVECv10On09EACnnrXC4Xnj59yurG06dP0d/fj0AgAB6Ph5KSEtTU1LBgs3v37o3Wg2z8oLOSxcVFDA0NYXBwEIODgxgYGMDg4CCGhoYQCAQAACqVCjqdDoWFhSgsLMTOnTvZI13ummwEgUAA4+PjeP78edzj2NgYRkZGsLi4CADIy8uLC/JFRUUoKSmBSqVK8m9AYoVCIZhMJtY8Pnv2DEajEdPT0wAAmUyGkpIS6HQ6aLVa7Nq1iz3SRQ/ImxIKhTAyMoLR0VGMjIyw5yaTCQ6HAwCQk5OD8vJylJWVsYa6tLSU3odJNjU1BYPBAIPBgGfPnsFgMGBoaAjhcBgCgSCufmi1WhQVFUGr1SZ7xJxscuFwGOPj4xgeHsbw8DCrGyaTCTabDQAgl8tRXl7OBu/Ky8tRWlq6GQ6D35xBZzXLy8uYmJhgo99jY2OseZyYmMDCwgIAQCgUvhSAtm/fjtzcXOTm5kKj0dBx9+S1uVwuWK1WmM1m2Gw2TExMxAUau93OtlUoFHFhnAszRUVF7+zJvVuF2+1mo2Emkwmjo6MYHR3F5OQklpaWAPxzaC7XvGi1WuzYsQP5+fnIz8+HRqOhkVzCLC8vw2azYWpqCmazGZOTk3HBxmKxYHl5GSkpKdi+fTsL00VFRWx2Ua1WJ/vXIP+jxcVFmEwmNgs3MjKC4eFhjI6OIhQKAfhndp+rHTqdDgUFBSgoKEB+fj5yc3OpfhB4PB5MTU1hcnISk5OTGB0dZaFmYmKCfRZpNBr2PioqKmLhZhMPrG6toLOW5eVlTE9PY3x8fMVRdLvdjkgkwrZXKpVQq9UsAGk0GuTl5UGtVkOj0eC9996DXC4Hn89P4m9FkmF+fh52ux1OpxNWqzUuzHCPFouFfQgBgFQqxY4dO+LCdexzsVicxN+IJAM3isaNnnHN6ujoKMxmM8LhMACAx+NBrVaz8JOXl4e8vDzs2LEDubm5UKlUVIu2iGg0CofDAYfDAYvFArPZDLPZjKmpKRZsLBYLm+3l3htcmImdJdRqtXTY2RYWjUZhNptZ8Il9nJycjHuPaDQa5Ofnxw2gcLVEoVBAoVDQeZybWCAQgNVqhd1uZ7WCWyYmJmA2mzE/P8+2l8vl2LlzJ3Q6XdwMoU6n24o3v313gs6/iUQimJmZiWtYrVYrpqenYbFYWDPLHRrHyc7OhkKhYMGHazq4RaVSISsrCzKZDDKZjA4N2GDC4TA8Hg/cbjfcbjecTidmZ2fhcDgwMzPDXnPBxul0splBAEhNTYVSqYwLwxqN5qWAvAWLB1lH0WgUdrudfUhxS+zr2dnZuK/hGhaVSgWVSgWFQoHc3Fy2Ti6XIzs7G9nZ2RSs36JQKIS5uTnMzc2xWV6HwwG73Q6bzQaHw8HWORyOuAG3zMxMFmoTQy4320cBlyTiZv0mJydZQOaaXq4BdrvdbHsej8fqhEajiasdarUaKpUKOTk5yM7ORk5ODs0QvQVer5fVDbvdzuqE3W6Pqx3T09PsHDsA4PP5yM3NZcE2MdwWFBRshsPN3iQKOv+vFy9ewGazYXZ2Fk6nkzXAKzXHTqcT0Wg07ut5PB4LPTKZDFlZWXFBiFsnFoshEokgk8kgEokgEomQlZUFkUgEoVCIrKysJP0PbAyhUAjBYBButxvBYBChUIg9DwaD8Hg88Pl88Hg8LMhwz2Nf+/3+l763WCyGXC6HUqlkgZVrIrnX3L8plUpqNEhSBINBmM1mVndim+aZmZm4Bpob3eUIBAJkZ2cjKyuLhZ/ERSqVQiwWQyqVIjMzE2KxGGKxGBkZGcjIyNhoJ5yui+XlZVZL/H4//H4/3G43e841I263mzUliUswGIz7nmlpaax2cE1lbDiNbTRpYIysF6/XC4vFElczbDYb7HY7ZmZm4sJ3YpuYkZHBQg8XgGIXrkZkZGRAIpGwusHVkXdhpjG2bvh8PlY3fD4fqxsul2vFmuFyudiMPkckEkGtVkOtVscFUY1GA6VSCZVKxULpu1Cb/w8UdNZTNBrF7Owsa6oTm+3YmYTEdYFA4KXZo0RcAJLJZNi2bRsEAgHS0tLYOR0SiQR8Pv9f13HEYvGq5ybxeLxVP3TD4XDciEKi+fn5uFHK2O29Xi+Wlpb+dZ3H44kLMWu9bVNSUiCTySCRSOLCY2KYXOm1XC6nm0KSLcflcmF2dnbVpnyldT6fL+7wy0RCoRBisRiZmZmQSqVIS0tjdSg9PR1isZj9LQJg23CDNRxu25UkbstZq+ZwgyCcxcVF+P1+FlqA/9YYbpBkaWkJXq8X0WgUL168gNfrhd/vX7MG8/l8SKXSVcPiausVCgXdNJJsGktLS3A4HCs25S6Xa8X18/PzcYdKJeL6j8zMTEgkEggEAgiFQohEorjehBvQXa12cF+zktUGY/x+/0sDP5zYWS4ArNfw+XwIh8OstnD1J7amcAOnPp9vzX4oPT0dUqmU1YOVgmLskpOTA6VSSefqvjoKOhtdbIOfOGMRCAQQDAbx4sUL9sfLfagD/8w+RaPRuHVc6Ihdx0n8I4+V2DwkWmuGKbE4xYYmLlyttC41NZXdTyAjIwMikYg1ViKRCNu2bVt1xosQ8vq4AMDVGL/fj/n5+RVfR6NRFiC4ehGJRFjDw22T2Gis1XgkDpLEWq3mJA7KxDZOMpkMKSkprMZwISu21nAz6mKxGBKJ5KXX3CAKXbCGkLXNz8+zxp+rI9zr2FmOxcVFBAIBLCwssN6EG3QAVq8dXL1JFBtAEgkEglUHM7mBYA4XlhIHcGLrRWZmJlJTU9nslUQiiQtx3MLVEaobbx0FHUIIIYQQQsiW00mX2SCEEEIIIYRsORR0CCGEEEIIIVsOBR1CCCGEEELIlpMG4Idk7wQhhBBCCCGEvEHD/wH9EfJ15x9njAAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "numba_distance_node = {\n",
    "    'id': 'distance_by_numba',\n",
    "    'type': NumbaDistanceNode,\n",
    "    'conf': {},\n",
    "    'inputs': ['points']\n",
    "}\n",
    "\n",
    "cupy_distance_node = {\n",
    "    'id': 'distance_by_cupy',\n",
    "    'type': CupyDistanceNode,\n",
    "    'conf': {},\n",
    "    'inputs': ['points']\n",
    "}\n",
    "\n",
    "task_list = [input_node, numba_distance_node,\n",
    "             cupy_distance_node, cudf_distance_node]\n",
    "out_list = ['distance_by_numba', 'distance_by_cupy', 'distance_by_cudf']\n",
    "task_graph = TaskGraph(task_list)\n",
    "task_graph.draw(show='ipynb')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we run the tasks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_w_numba, df_w_cupy, df_w_cudf = task_graph.run(out_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use `verify` function defined above to verify the results:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Max Difference: 2.220446049250313e-16\n",
      "Max Difference: 2.220446049250313e-16\n"
     ]
    }
   ],
   "source": [
    "mdiff = verify(df_w_cudf['distance_cudf'], df_w_numba['distance_numba'])\n",
    "print('Max Difference: {}'.format(mdiff))\n",
    "mdiff = verify(df_w_cudf['distance_cudf'], df_w_cupy['distance_cupy'])\n",
    "print('Max Difference: {}'.format(mdiff))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Dask distributed computation\n",
    "\n",
    "Using Dask and `dask-cudf` we can run the Nodes with customized GPU kernels on distributed dataframes. Under the hood of the `Node` class the Dask delayed processing API is handled for cudf dataframes when the `self.delayed_process = True` flag is set.\n",
    "\n",
    "We first start a distributed Dask environment. When a dask client is instantiated it registers itself as the default Dask scheduler (<http://distributed.dask.org/en/latest/client.html>). Therefore all subsequent Dask distibuted dataframe operations will run in distributed fashion."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/opt/conda/envs/rapids/lib/python3.6/site-packages/distributed/dashboard/core.py:79: UserWarning: \n",
      "Port 8787 is already in use. \n",
      "Perhaps you already have a cluster running?\n",
      "Hosting the diagnostics dashboard on a random port instead.\n",
      "  warnings.warn(\"\\n\" + msg)\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<table style=\"border: 2px solid white;\">\n",
       "<tr>\n",
       "<td style=\"vertical-align: top; border: 0px solid white\">\n",
       "<h3 style=\"text-align: left;\">Client</h3>\n",
       "<ul style=\"text-align: left; list-style: none; margin: 0; padding: 0;\">\n",
       "  <li><b>Scheduler: </b>tcp://127.0.0.1:33519</li>\n",
       "  <li><b>Dashboard: </b><a href='http://127.0.0.1:35345/status' target='_blank'>http://127.0.0.1:35345/status</a>\n",
       "</ul>\n",
       "</td>\n",
       "<td style=\"vertical-align: top; border: 0px solid white\">\n",
       "<h3 style=\"text-align: left;\">Cluster</h3>\n",
       "<ul style=\"text-align: left; list-style:none; margin: 0; padding: 0;\">\n",
       "  <li><b>Workers: </b>4</li>\n",
       "  <li><b>Cores: </b>4</li>\n",
       "  <li><b>Memory: </b>270.39 GB</li>\n",
       "</ul>\n",
       "</td>\n",
       "</tr>\n",
       "</table>"
      ],
      "text/plain": [
       "<Client: 'tcp://127.0.0.1:33519' processes=4 threads=4, memory=270.39 GB>"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from dask_cuda import LocalCUDACluster\n",
    "cluster = LocalCUDACluster()\n",
    "from dask.distributed import Client\n",
    "client = Client(cluster)\n",
    "client"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Dask status page can be displayed in a web browser at `<ip-address>:8787`. The ip-address corresponds to the machine where the dask cluster (scheduler) was launched. Most likely same ip-address as where this jupyter notebook is running. The javascript cell below will launch the dask status page otherwise manually go to the status page <http://distributed.dask.org/en/latest/web.html>. Using the Dask status page is convenient for monitoring dask distributed processing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<script type=\"text/Javascript\">\n",
       "    function check_status(){\n",
       "        var url = document.location.href;\n",
       "        var index = url.indexOf(':8888');\n",
       "        var status = url.substr(0, index)+\":8787\";\n",
       "        window.open(status,'_blank');\n",
       "    }\n",
       "    check_status();\n",
       "</script>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from IPython.display import HTML\n",
    "javascript = \"\"\"\n",
    "<script type=\"text/Javascript\">\n",
    "    function check_status(){\n",
    "        var url = document.location.href;\n",
    "        var index = url.indexOf(':8888');\n",
    "        var status = url.substr(0, index)+\":8787\";\n",
    "        window.open(status,'_blank');\n",
    "    }\n",
    "    check_status();\n",
    "</script>\n",
    "\"\"\"\n",
    "HTML(javascript)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The next step is to partition the `cudf` dataframe into a `dask_cudf` dataframe. Here we make the number of partitions correspond to the number of workers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DistributedNode(Node):\n",
    "\n",
    "    def columns_setup(self,):\n",
    "        self.required = {'x': 'float64',\n",
    "                         'y': 'float64'}\n",
    "\n",
    "    def process(self, inputs):\n",
    "        npartitions = self.conf['npartitions']\n",
    "        df = inputs[0]        \n",
    "        return dask_cudf.from_cudf(df, npartitions=npartitions)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We add this distribution node to the computation graph to convert `cudf` dataframes into `dask-cudf` dataframes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "npartitions = len(client.scheduler_info()['workers'])\n",
    "\n",
    "input_node = {\n",
    "    'id': 'points',\n",
    "    'type': PointNode,\n",
    "    'conf': {},\n",
    "    'inputs': []\n",
    "}\n",
    "\n",
    "distributed_node = {\n",
    "    'id': 'distributed_points',\n",
    "    'type': DistributedNode,\n",
    "    'conf': {'npartitions': npartitions},\n",
    "    'inputs': [\"points\"]\n",
    "}\n",
    "\n",
    "cudf_distance_node = {\n",
    "    'id': 'distance_by_cudf',\n",
    "    'type': DistanceNode,\n",
    "    'conf': {},\n",
    "    'inputs': ['points']\n",
    "}\n",
    "\n",
    "numba_distance_node = {\n",
    "    'id': 'distance_by_numba',\n",
    "    'type': NumbaDistanceNode,\n",
    "    'conf': {},\n",
    "    'inputs': ['distributed_points']\n",
    "}\n",
    "\n",
    "cupy_distance_node = {\n",
    "    'id': 'distance_by_cupy',\n",
    "    'type': CupyDistanceNode,\n",
    "    'conf': {},\n",
    "    'inputs': ['distributed_points']\n",
    "}\n",
    "\n",
    "task_list = [input_node, distributed_node, cudf_distance_node,\n",
    "             numba_distance_node, cupy_distance_node]\n",
    "out_list = ['distance_by_numba', 'distance_by_cupy', 'distance_by_cudf']\n",
    "task_graph = TaskGraph(task_list)\n",
    "task_graph.draw(show='ipynb')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4\n"
     ]
    }
   ],
   "source": [
    "print(npartitions)\n",
    "df_w_numba, df_w_cupy, df_w_cudf = task_graph.run(out_list)\n",
    "#df_w_numba = task_graph.run(out_list)\n",
    "df_w_numba = df_w_numba.compute()\n",
    "df_w_cupy = df_w_cupy.compute()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Verify the results:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Max Difference: 2.220446049250313e-16\n",
      "Max Difference: 2.220446049250313e-16\n"
     ]
    }
   ],
   "source": [
    "mdiff = verify(df_w_cudf['distance_cudf'], df_w_numba['distance_numba'])\n",
    "print('Max Difference: {}'.format(mdiff))\n",
    "mdiff = verify(df_w_cudf['distance_cudf'], df_w_cupy['distance_cupy'])\n",
    "print('Max Difference: {}'.format(mdiff))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One limitation to be aware of when using customized kernels within Nodes in the Dask environment, is that each GPU kernel works on one partition of the dataframe. Therefore if the computation depends on other partitions of the dataframe the approach above does not work."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Saving Custom Nodes and Kernels\n",
    "\n",
    "The gQuant framework already implements a number of Nodes. These can be found in `gquant.plugin_nodes` submodules. Refer to those nodes for reference implementations. For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "class CsvStockLoader(Node):\n",
      "\n",
      "    def columns_setup(self):\n",
      "        self.required = {}\n",
      "        self.addition = {\"datetime\": \"date\",\n",
      "                         \"asset\": \"int64\",\n",
      "                         \"volume\": \"float64\",\n",
      "                         \"close\": \"float64\",\n",
      "                         \"open\": \"float64\",\n",
      "                         \"high\": \"float64\",\n",
      "                         \"low\": \"float64\"}\n",
      "        self.deletion = None\n",
      "        self.retention = None\n",
      "\n",
      "    def process(self, inputs):\n",
      "        \"\"\"\n",
      "        Load the end of day stock CSV data into cuDF dataframe\n",
      "\n",
      "        Arguments\n",
      "        -------\n",
      "         inputs: list\n",
      "             empty list\n",
      "        Returns\n",
      "        -------\n",
      "        cudf.DataFrame\n",
      "        \"\"\"\n",
      "        df = cudf.read_csv(self.conf['path'])\n",
      "        # extract the year, month, day\n",
      "        ymd = df['DTE'].astype('str').str.extract(r'(\\d\\d\\d\\d)(\\d\\d)(\\d\\d)')\n",
      "        # construct the standard datetime str\n",
      "        df['DTE'] = ymd[0].str.cat(ymd[1],\n",
      "                                   '-').str.cat(ymd[2],\n",
      "                                                '-').astype('datetime64[ms]')\n",
      "        df = df[['DTE', 'OPEN', 'CLOSE', 'HIGH', 'LOW', 'SM_ID', 'VOLUME']]\n",
      "        df['VOLUME'] /= 1000\n",
      "        # change the names\n",
      "        df.columns = ['datetime', 'open', 'close',\n",
      "                      'high', 'low', \"asset\", 'volume']\n",
      "        return df\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import inspect\n",
    "from gquant.plugin_nodes.dataloader import CsvStockLoader\n",
    "\n",
    "print(inspect.getsource(CsvStockLoader))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The customized kernels and nodes can be saved to your own python modules for future re-use instead of having to re-define them at runtime. We will take the nodes we defined above and write them to a python module. Then we will re-run our workflow importing the Nodes from the custom module we wrote out."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Overwriting custom_nodes.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile custom_nodes.py\n",
    "\n",
    "import math\n",
    "import numpy as np\n",
    "from numba import cuda\n",
    "import cupy\n",
    "import cudf\n",
    "import dask_cudf\n",
    "\n",
    "from gquant.dataframe_flow import Node\n",
    "import rmm\n",
    "\n",
    "\n",
    "class PointNode(Node):\n",
    "\n",
    "    def columns_setup(self,):\n",
    "        self.required = {}\n",
    "        self.addition = {'x': 'float64',\n",
    "                         'y': 'float64'}\n",
    "\n",
    "    def process(self, inputs):\n",
    "        df = cudf.DataFrame()\n",
    "        df['x'] = np.random.rand(1000)\n",
    "        df['y'] = np.random.rand(1000)\n",
    "        return df\n",
    "\n",
    "\n",
    "class DistanceNode(Node):\n",
    "\n",
    "    def columns_setup(self,):\n",
    "        self.required = {'x': 'float64',\n",
    "                         'y': 'float64'}\n",
    "        self.addition = {'distance_cudf': 'float64'}\n",
    "\n",
    "    def process(self, inputs):\n",
    "        df = inputs[0]\n",
    "        df['distance_cudf'] = (df['x']**2 + df['y']**2).sqrt()\n",
    "        return df\n",
    "\n",
    "\n",
    "@cuda.jit\n",
    "def distance_kernel(x, y, distance, array_len):\n",
    "    # ii - overall thread index\n",
    "    ii = cuda.threadIdx.x + cuda.blockIdx.x * cuda.blockDim.x\n",
    "    if ii < array_len:\n",
    "        distance[ii] = math.sqrt(x[ii]**2 + y[ii]**2)\n",
    "\n",
    "\n",
    "class NumbaDistanceNode(Node):\n",
    "\n",
    "    def columns_setup(self,):\n",
    "        self.required = {'x': 'float64',\n",
    "                         'y': 'float64'}\n",
    "        self.addition = {'distance_numba': 'float64'}\n",
    "        self.delayed_process = True\n",
    "\n",
    "    def process(self, inputs):\n",
    "        df = inputs[0]\n",
    "        number_of_threads = 16\n",
    "        number_of_blocks = ((len(df) - 1)//number_of_threads) + 1\n",
    "        # Inits device array by setting 0 for each index.\n",
    "        # df['distance_numba'] = 0.0\n",
    "        darr = rmm.device_array(len(df))\n",
    "        distance_kernel[(number_of_blocks,), (number_of_threads,)](\n",
    "            df['x'],\n",
    "            df['y'],\n",
    "            darr,\n",
    "            len(df))\n",
    "        df['distance_numba'] = darr\n",
    "        return df\n",
    "\n",
    "kernel_string = r'''\n",
    "    extern \"C\" __global__\n",
    "    void compute_distance(const double* x, const double* y,\n",
    "            double* distance, int arr_len) {\n",
    "        int tid = blockDim.x * blockIdx.x + threadIdx.x;\n",
    "        if (tid < arr_len){\n",
    "        distance[tid] = sqrt(x[tid]*x[tid] + y[tid]*y[tid]);\n",
    "        }\n",
    "    }\n",
    "'''\n",
    "               \n",
    "\n",
    "\n",
    "class CupyDistanceNode(Node):\n",
    "\n",
    "    def columns_setup(self,):\n",
    "        self.required = {'x': 'float64',\n",
    "                         'y': 'float64'}\n",
    "        self.addition = {'distance_cupy': 'float64'}\n",
    "        self.delayed_process = True\n",
    "               \n",
    "    def get_kernel(self):\n",
    "        raw_kernel = cupy.RawKernel(kernel_string, 'compute_distance')\n",
    "        return raw_kernel\n",
    "\n",
    "    def process(self, inputs):\n",
    "        df = inputs[0]\n",
    "        # cupy_x = cupy.asarray(df['x'.to_gpu_array())\n",
    "        # cupy_y = cupy.asarray(df['y'.to_gpu_array())\n",
    "        cupy_x = cupy.asarray(df['x'])\n",
    "        cupy_y = cupy.asarray(df['y'])\n",
    "        number_of_threads = 16\n",
    "        number_of_blocks = ((len(df) - 1)//number_of_threads) + 1\n",
    "        dis = cupy.ndarray(len(df), dtype=cupy.float64)\n",
    "        self.get_kernel()((number_of_blocks,), (number_of_threads,),\n",
    "                   (cupy_x, cupy_y, dis, len(df)))\n",
    "        df['distance_cupy'] = dis\n",
    "        return df\n",
    "\n",
    "\n",
    "class DistributedNode(Node):\n",
    "\n",
    "    def columns_setup(self,):\n",
    "        self.required = {'x': 'float64',\n",
    "                         'y': 'float64'}\n",
    "\n",
    "    def process(self, inputs):\n",
    "        npartitions = self.conf['npartitions']\n",
    "        df = inputs[0]\n",
    "        return dask_cudf.from_cudf(df, npartitions=npartitions)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When defining the tasks we specify `filepath` for the path to the python module that has the Node definition. Notice, that the `type` is specified as a string instead of class. The string is the class name of the node that will be imported for running a task."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "npartitions = len(client.scheduler_info()['workers'])\n",
    "\n",
    "input_node = {\n",
    "    'id': 'points',\n",
    "    'type': 'PointNode',\n",
    "    'conf': {},\n",
    "    'inputs': [],\n",
    "    'filepath': 'custom_nodes.py'\n",
    "}\n",
    "\n",
    "distributed_node = {\n",
    "    'id': 'distributed_points',\n",
    "    'type': 'DistributedNode',\n",
    "    'conf': {'npartitions': npartitions},\n",
    "    'inputs': ['points'],\n",
    "    'filepath': 'custom_nodes.py'\n",
    "}\n",
    "\n",
    "cudf_distance_node = {\n",
    "    'id': 'distance_by_cudf',\n",
    "    'type': 'DistanceNode',\n",
    "    'conf': {},\n",
    "    'inputs': ['points'],\n",
    "    'filepath': 'custom_nodes.py'\n",
    "}\n",
    "\n",
    "numba_distance_node = {\n",
    "    'id': 'distance_by_numba',\n",
    "    'type': 'NumbaDistanceNode',\n",
    "    'conf': {},\n",
    "    'inputs': ['distributed_points'],\n",
    "    'filepath': 'custom_nodes.py'\n",
    "}\n",
    "\n",
    "cupy_distance_node = {\n",
    "    'id': 'distance_by_cupy',\n",
    "    'type': 'CupyDistanceNode',\n",
    "    'conf': {},\n",
    "    'inputs': ['distributed_points'],\n",
    "    'filepath': 'custom_nodes.py'\n",
    "}\n",
    "\n",
    "task_list = [input_node, distributed_node, cudf_distance_node,\n",
    "             numba_distance_node, cupy_distance_node]\n",
    "out_list = ['distance_by_numba', 'distance_by_cupy', 'distance_by_cudf']\n",
    "task_graph = TaskGraph(task_list)\n",
    "task_graph.draw(show='ipynb')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Max Difference: 2.220446049250313e-16\n",
      "Max Difference: 2.220446049250313e-16\n"
     ]
    }
   ],
   "source": [
    "df_w_numba, df_w_cupy, df_w_cudf = task_graph.run(out_list)\n",
    "df_w_numba = df_w_numba.compute()\n",
    "df_w_cupy = df_w_cupy.compute()\n",
    "\n",
    "mdiff = verify(df_w_cudf['distance_cudf'], df_w_numba['distance_numba'])\n",
    "print('Max Difference: {}'.format(mdiff))\n",
    "mdiff = verify(df_w_cudf['distance_cudf'], df_w_cupy['distance_cupy'])\n",
    "print('Max Difference: {}'.format(mdiff))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Conclusion\n",
    "\n",
    "Using customized GPU kernels allows data scientists to implement and incorporate advanced algorithms. We demonstrated implementations using Numba and CuPy.\n",
    "\n",
    "The Numba approach enables data scientists to write GPU kernels directly in the Python language. Numba is easy to use for implementing and accelerating computations. However there is some overhead incurred for compiling the kernels whenever the Numba GPU kernels are used for the first time in a Python process. Currently Numba library only supports primitive data types. Some advanced CUDA programming features, such as function pointers and function recursions are not supported. \n",
    "\n",
    "The Cupy method is very flexible, because data scientists are writing C/C++ GPU kernels with CUDA directly. All the CUDA programming features are supported. CuPy compiles the kernel and caches the device code to the filesystem. The launch overhead is low. Also, the GPU kernel is built statically resulting in runtime efficiency. However it might be harder for data scientists to use, because C/C++ programming is more complicated. \n",
    "\n",
    "Below is a brief summary comparison table:\n",
    "\n",
    "| Methods | Development Difficulty | Flexibility | Efficiency | Latency |\n",
    "|---|---|---|---|---|\n",
    "| Numba method | medium | medium | low | high |\n",
    "| CuPy method | hard | high  | high | low |\n",
    "\n",
    "We recommend that the data scientists select the approach appropriate for their task taking into consideration the efficiency, latency, difficulty and flexibility of their workflow. \n",
    "\n",
    "In this blog, we showed how to wrap the customized GPU kernels in gQuant nodes. Also, by taking advantage of having the gQuant handle the low-level Dask interfaces for the developer, we demonstrated how to use the gQuant workflow with Dask distributed computations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Clean up\n",
    "\n",
    "# Shutdown the Dask cluster\n",
    "client.close()\n",
    "cluster.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
